Skip to content
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
57 changes: 48 additions & 9 deletions kinode/packages/app_store/app_store/src/http_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,38 @@ fn serve_paths(
}
Method::PUT => {
// update an app
// TODO
Ok((StatusCode::NO_CONTENT, None, format!("TODO").into_bytes()))
let pkg_listing: &PackageListing = state
.get_listing(&package_id)
.ok_or(anyhow::anyhow!("No package"))?;
let pkg_state: &PackageState = state
.downloaded_packages
.get(&package_id)
.ok_or(anyhow::anyhow!("No package"))?;
let download_from = pkg_state
.mirrored_from
.as_ref()
.ok_or(anyhow::anyhow!("No mirror for package {package_id}"))?
.to_string();
match crate::start_download(
our,
requested_packages,
&package_id,
&download_from,
pkg_state.mirroring,
pkg_state.auto_update,
&None,
) {
DownloadResponse::Started => Ok((
StatusCode::CREATED,
None,
format!("Downloading").into_bytes(),
)),
DownloadResponse::Failure => Ok((
StatusCode::SERVICE_UNAVAILABLE,
None,
format!("Failed to download").into_bytes(),
)),
}
}
Method::DELETE => {
// uninstall an app
Expand Down Expand Up @@ -235,30 +265,39 @@ fn serve_paths(
}
Method::POST => {
// download an app
// TODO get fields from POST body
let pkg_listing: &PackageListing = state
.get_listing(&package_id)
.ok_or(anyhow::anyhow!("No package"))?;
// from POST body, look for download_from field and use that as the mirror
let body = crate::get_blob()
.ok_or(anyhow::anyhow!("missing blob"))?
.bytes;
let body_json: serde_json::Value =
serde_json::from_slice(&body).unwrap_or_default();
let mirrors: &Vec<NodeId> = pkg_listing
.metadata
.as_ref()
.ok_or(anyhow::anyhow!("No metadata for package {package_id}"))?
.mirrors
.as_ref()
.ok_or(anyhow::anyhow!("No mirrors for package {package_id}"))?;
// TODO select on FE
let download_from = mirrors
.first()
.ok_or(anyhow::anyhow!("No mirrors for package {package_id}"))?;
// TODO select on FE
let download_from = body_json
.get("download_from")
.unwrap_or(&json!(mirrors
.first()
.ok_or(anyhow::anyhow!("No mirrors for package {package_id}"))?))
.as_str()
.ok_or(anyhow::anyhow!("download_from not a string"))?
.to_string();
// TODO select on FE? or after download but before install?
let mirror = false;
let auto_update = false;
let desired_version_hash = None;
match crate::start_download(
our,
requested_packages,
&package_id,
download_from,
&download_from,
mirror,
auto_update,
&desired_version_hash,
Expand Down
43 changes: 36 additions & 7 deletions kinode/packages/app_store/app_store/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ fn handle_message(
if source.node() != our.node() || source.process != "eth:distro:sys" {
return Err(anyhow::anyhow!("eth sub event from weird addr: {source}"));
}
handle_eth_sub_event(&mut state, e)?;
handle_eth_sub_event(our, &mut state, e)?;
}
Req::Http(incoming) => {
if source.node() != our.node()
Expand All @@ -187,7 +187,7 @@ fn handle_message(
return Err(anyhow::anyhow!("http_server from non-local node"));
}
if let HttpServerRequest::Http(req) = incoming {
http_api::handle_http_request(&our, &mut state, requested_packages, &req)?;
http_api::handle_http_request(our, &mut state, requested_packages, &req)?;
}
}
},
Expand Down Expand Up @@ -269,6 +269,7 @@ fn handle_local_request(
our_version,
installed: false,
caps_approved: true, // TODO see if we want to auto-approve local installs
manifest_hash: None, // generated in the add fn
mirroring: *mirror,
auto_update: false, // can't auto-update a local package
metadata: None, // TODO
Expand Down Expand Up @@ -407,7 +408,7 @@ fn handle_receive_download(
Some(hash) => {
if download_hash != hash {
return Err(anyhow::anyhow!(
"app store: downloaded package is not latest version--rejecting download!"
"app store: downloaded package is not desired version--rejecting download! download hash: {download_hash}, desired hash: {hash}"
));
}
}
Expand All @@ -422,7 +423,7 @@ fn handle_receive_download(
if let Some(latest_hash) = metadata.versions.clone().unwrap_or(vec![]).last() {
if &download_hash != latest_hash {
return Err(anyhow::anyhow!(
"app store: downloaded package is not latest version--rejecting download!"
"app store: downloaded package is not latest version--rejecting download! download hash: {download_hash}, latest hash: {latest_hash}"
));
}
} else {
Expand All @@ -436,19 +437,43 @@ fn handle_receive_download(
}
}

let old_manifest_hash = match state.downloaded_packages.get(&package_id) {
Some(package_state) => package_state
.manifest_hash
.clone()
.unwrap_or("OLD".to_string()),
_ => "OLD".to_string(),
};

state.add_downloaded_package(
&package_id,
PackageState {
mirrored_from: Some(requested_package.from),
our_version: download_hash,
installed: false,
caps_approved: false,
manifest_hash: None, // generated in the add fn
mirroring: requested_package.mirror,
auto_update: requested_package.auto_update,
metadata: None, // TODO
},
Some(blob.bytes),
)
)?;

let new_manifest_hash = match state.downloaded_packages.get(&package_id) {
Some(package_state) => package_state
.manifest_hash
.clone()
.unwrap_or("NEW".to_string()),
_ => "NEW".to_string(),
};

// lastly, if auto_update is true, AND the caps_hash has NOT changed,
// trigger install!
if requested_package.auto_update && old_manifest_hash == new_manifest_hash {
handle_install(our, state, &package_id)?;
}
Ok(())
}

fn handle_ft_worker_result(body: &[u8], context: &[u8]) -> anyhow::Result<()> {
Expand All @@ -470,11 +495,15 @@ fn handle_ft_worker_result(body: &[u8], context: &[u8]) -> anyhow::Result<()> {
Ok(())
}

fn handle_eth_sub_event(state: &mut State, event: EthSubEvent) -> anyhow::Result<()> {
fn handle_eth_sub_event(
our: &Address,
state: &mut State,
event: EthSubEvent,
) -> anyhow::Result<()> {
let EthSubEvent::Log(log) = event else {
return Err(anyhow::anyhow!("app store: got non-log event"));
};
state.ingest_listings_contract_event(log)
state.ingest_listings_contract_event(our, log)
}

fn fetch_package_manifest(package: &PackageId) -> anyhow::Result<Vec<kt::PackageManifestEntry>> {
Expand Down
49 changes: 47 additions & 2 deletions kinode/packages/app_store/app_store/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::LocalRequest;
use alloy_rpc_types::Log;
use alloy_sol_types::{sol, SolEvent};
use kinode_process_lib::kernel_types as kt;
Expand Down Expand Up @@ -86,6 +87,7 @@ pub struct PackageState {
pub our_version: String,
pub installed: bool,
pub caps_approved: bool,
pub manifest_hash: Option<String>,
/// are we serving this package to others?
pub mirroring: bool,
/// if we get a listing data update, will we try to download it?
Expand Down Expand Up @@ -171,7 +173,7 @@ impl State {
pub fn add_downloaded_package(
&mut self,
package_id: &PackageId,
package_state: PackageState,
mut package_state: PackageState,
package_bytes: Option<Vec<u8>>,
) -> anyhow::Result<()> {
if let Some(package_bytes) = package_bytes {
Expand Down Expand Up @@ -215,6 +217,13 @@ impl State {
})?)
.blob(blob)
.send_and_await_response(5)??;

let manifest_file = vfs::File {
path: format!("/{}/pkg/manifest.json", package_id),
};
let manifest_bytes = manifest_file.read()?;
let manifest_hash = generate_metadata_hash(&manifest_bytes);
package_state.manifest_hash = Some(manifest_hash);
}
self.downloaded_packages
.insert(package_id.to_owned(), package_state);
Expand Down Expand Up @@ -296,6 +305,10 @@ impl State {
// generate entry from this data
// for the version hash, take the SHA-256 hash of the zip file
let our_version = generate_version_hash(&zip_file_bytes);
let manifest_file = vfs::File {
path: format!("/{}/pkg/manifest.json", package_id),
};
let manifest_bytes = manifest_file.read()?;
// the user will need to turn mirroring and auto-update back on if they
// have to reset the state of their app store for some reason. the apps
// themselves will remain on disk unless explicitly deleted.
Expand All @@ -306,6 +319,7 @@ impl State {
our_version,
installed: true,
caps_approved: true, // since it's already installed this must be true
manifest_hash: Some(generate_metadata_hash(&manifest_bytes)),
mirroring: false,
auto_update: false,
metadata: None,
Expand Down Expand Up @@ -362,7 +376,11 @@ impl State {
}

/// only saves state if last_saved_block is more than 1000 blocks behind
pub fn ingest_listings_contract_event(&mut self, log: Log) -> anyhow::Result<()> {
pub fn ingest_listings_contract_event(
&mut self,
our: &Address,
log: Log,
) -> anyhow::Result<()> {
let block_number: u64 = log
.block_number
.ok_or(anyhow::anyhow!("app store: got log with no block number"))?
Expand Down Expand Up @@ -454,6 +472,33 @@ impl State {

current_listing.metadata_hash = metadata_hash;
current_listing.metadata = metadata;

let package_id = PackageId::new(&current_listing.name, &current_listing.publisher);

// if we have this app installed, and we have auto_update set to true,
// we should try to download new version from the mirrored_from node
// and install it if successful.
if let Some(package_state) = self.downloaded_packages.get(&package_id) {
if package_state.auto_update {
if let Some(mirrored_from) = &package_state.mirrored_from {
crate::print_to_terminal(
1,
&format!(
"app store: auto-updating package {package_id} from {mirrored_from}"
),
);
Request::to(our)
.body(serde_json::to_vec(&LocalRequest::Download {
package: package_id,
download_from: mirrored_from.clone(),
mirror: package_state.mirroring,
auto_update: package_state.auto_update,
desired_version_hash: None,
})?)
.send()?;
}
}
}
}
Transfer::SIGNATURE_HASH => {
let from = alloy_primitives::Address::from_word(log.topics[1]);
Expand Down