Merge pull request #232 from kinode-dao/0.5.3

0.5.3
This commit is contained in:
doria 2024-02-07 12:32:09 -03:00 committed by GitHub
commit acc1a942e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
146 changed files with 11539 additions and 11616 deletions

12
.gitignore vendored
View File

@ -1,5 +1,8 @@
target/
wit/
**/target/
**/wit/
**/*.wasm
.vscode
.app-signing
.DS_Store
@ -7,9 +10,8 @@ wit/
*.swo
*.zip
/home
modules/**/pkg/*.wasm
modules/**/wit
target.wasm
world
packages/**/pkg/*.wasm
packages/**/wit
.env
src/bootstrapped_processes.rs
kinode/src/bootstrapped_processes.rs
kinode/packages/**/wasi_snapshot_preview1.wasm

2864
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,75 +1,33 @@
[package]
name = "kinode"
name = "kinode_lib"
authors = ["KinodeDAO"]
version = "0.5.2"
version = "0.5.3"
edition = "2021"
description = "A general-purpose sovereign cloud computing platform"
homepage = "https://kinode.org"
repository = "https://github.com/kinode-dao/kinode"
license = "Apache-2.0"
[build-dependencies]
reqwest = { version = "0.11.22", features = ["blocking"] }
sha2 = "0.10"
walkdir = "2.4"
zip = "0.6"
[features]
simulation-mode = []
[dependencies]
aes-gcm = "0.10.2"
anyhow = "1.0.71"
async-trait = "0.1.71"
base64 = "0.13"
bincode = "1.3.3"
blake3 = "1.4.1"
bytes = "1.4.0"
cap-std = "2.0.0"
chacha20poly1305 = "0.10.1"
chrono = "0.4.31"
clap = { version = "4.4", features = ["derive"] }
crossterm = { version = "0.26.1", features = ["event-stream", "bracketed-paste"] }
dashmap = "5.5.3"
digest = "0.10"
elliptic-curve = { version = "0.13.8", features = ["ecdh"] }
ethers = "2.0"
ethers-providers = "2.0.9"
flate2 = "1.0"
futures = "0.3"
generic-array = "0.14"
getrandom = "0.2.10"
hex = "0.4.3"
hkdf = "0.12.3"
hmac = "0.12"
http = "0.2.9"
jwt = "0.16"
lazy_static = "1.4.0"
log = "0.4.20"
nohash-hasher = "0.2.0"
num-traits = "0.2"
open = "5.0.0"
public-ip = "0.2.2"
rand = "0.8.4"
reqwest = "0.11.18"
ring = "0.16.20"
rmp-serde = "1.1.2"
rocksdb = { version = "0.21.0", features = ["multi-threaded-cf"] }
route-recognizer = "0.3.1"
rusqlite = { version = "0.30.0", features = ["bundled"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_urlencoded = "0.7"
sha2 = "0.10"
snow = { version = "0.9.3", features = ["ring-resolver"] }
static_dir = "0.2.0"
thiserror = "1.0"
tokio = { version = "1.28", features = ["fs", "macros", "rt-multi-thread", "signal", "sync"] }
tokio-stream = "0.1.14"
tokio-tungstenite = "0.20.1"
url = "2.4.1"
uuid = { version = "1.1.2", features = ["serde", "v4"] }
warp = "0.3.5"
wasmtime = "15.0.1"
wasmtime-wasi = "15.0.1"
zip = "0.6"
lib = { path = "lib" }
[workspace]
members = [
"lib", "kinode",
"kinode/packages/app_store/app_store", "kinode/packages/app_store/ft_worker",
"kinode/packages/app_store/download", "kinode/packages/app_store/install", "kinode/packages/app_store/uninstall",
"kinode/packages/chess/chess",
"kinode/packages/homepage/homepage",
"kinode/packages/kns_indexer/kns_indexer",
"kinode/packages/terminal/terminal",
"kinode/packages/terminal/alias", "kinode/packages/terminal/cat", "kinode/packages/terminal/echo", "kinode/packages/terminal/hi", "kinode/packages/terminal/m", "kinode/packages/terminal/top",
"kinode/packages/tester/tester", "kinode/packages/tester/test_runner",
]
default-members = ["lib"]
resolver = "2"
[profile.release]
strip = "symbols"
lto = true
panic = "abort"
codegen-units = 1

View File

@ -20,10 +20,6 @@ If you have questions, join the [Kinode discord](https://discord.gg/TCgdca5Bjt)
git clone git@github.com:kinode-dao/kinode.git
# Configure dependency retrieval from GitHub
mkdir .cargo
echo "net.git-fetch-with-cli = true" > .cargo/config
# Get some stuff so we can build Wasm.
cd kinode
@ -33,10 +29,10 @@ rustup target add wasm32-wasi
rustup target add wasm32-wasi --toolchain nightly
cargo install cargo-wasi
# Build the runtime, along with a number of booted-at-startup WASM modules including terminal and key_value
# Build the runtime, along with a number of "distro" WASM modules
# OPTIONAL: --release flag
cargo +nightly build --release
cargo +nightly build -p kinode
```
### Boot
@ -46,7 +42,8 @@ Make sure not to use the same home directory for two nodes at once! You can use
TODO: document feature flags `--simulation-mode`
```bash
cargo +nightly run --release -- home --rpc wss://eth-sepolia.g.alchemy.com/v2/<your-api-key> --tesnet
# OPTIONAL: --release flag
cargo +nightly run -p kinode -- home --rpc wss://<your-api-url> --testnet
```
On boot you will be prompted to navigate to `localhost:8080`. Make sure your ETH wallet is connected to the Sepolia test network. Login should be straightforward, just submit the transactions and follow the flow. If you want to register a new ID you will either need [Sepolia testnet tokens](https://www.infura.io/faucet/sepolia) or an invite code.

230
build.rs
View File

@ -1,230 +0,0 @@
use std::process::Command;
use std::{
fs, io,
io::{Read, Write},
};
fn run_command(cmd: &mut Command) -> io::Result<()> {
let status = cmd.status()?;
if status.success() {
Ok(())
} else {
Err(io::Error::new(io::ErrorKind::Other, "Command failed"))
}
}
fn file_outdated<P1, P2>(input: P1, output: P2) -> io::Result<bool>
where
P1: AsRef<std::path::Path>,
P2: AsRef<std::path::Path>,
{
let out_meta = std::fs::metadata(output);
if let Ok(meta) = out_meta {
let output_mtime = meta.modified()?;
// if input file is more recent than our output, we are outdated
let input_meta = fs::metadata(input)?;
let input_mtime = input_meta.modified()?;
Ok(input_mtime > output_mtime)
} else {
// output file not found, we are outdated
Ok(true)
}
}
fn build_app(target_path: &str, name: &str, parent_pkg_path: Option<&str>) {
let pwd = std::env::current_dir().unwrap();
let start = std::time::Instant::now();
// if and only if module's wit is outdated, re-set-up build environment
if file_outdated(
format!("{}/target.wasm", pwd.display()),
format!("{}/target/bindings/{}/target.wasm", target_path, name),
)
.unwrap_or(true)
{
// create target/bindings directory
fs::create_dir_all(format!("{}/target/bindings/{}", target_path, name,)).unwrap();
// copy newly-made target.wasm into target/bindings
run_command(Command::new("cp").args([
"target.wasm",
&format!("{}/target/bindings/{}/", target_path, name,),
]))
.unwrap();
// copy newly-made world into target/bindings
run_command(Command::new("cp").args([
"world",
&format!("{}/target/bindings/{}/", target_path, name,),
]))
.unwrap();
}
// Build the module targeting wasm32-wasi
let bash_build_path = &format!("{}/build.sh", target_path);
if std::path::Path::new(&bash_build_path).exists() {
let cwd = std::env::current_dir().unwrap();
std::env::set_current_dir(target_path).unwrap();
run_command(Command::new("/bin/bash").arg("build.sh")).unwrap();
std::env::set_current_dir(cwd).unwrap();
} else {
run_command(Command::new("cargo").args([
"+nightly",
"build",
"--release",
"--no-default-features",
&format!("--manifest-path={}/Cargo.toml", target_path),
"--target",
"wasm32-wasi",
]))
.unwrap();
}
// Adapt module to component with adapter based on wasi_snapshot_preview1.wasm
run_command(Command::new("wasm-tools").args([
"component",
"new",
&format!("{}/target/wasm32-wasi/release/{}.wasm", target_path, name),
"-o",
&format!(
"{}/target/wasm32-wasi/release/{}_adapted.wasm",
target_path, name
),
"--adapt",
&format!("{}/wasi_snapshot_preview1.wasm", pwd.display()),
]))
.unwrap();
// Determine the destination for the .wasm file after embedding wit
let wasm_dest_path = if let Some(parent_pkg) = parent_pkg_path {
format!("{}/{}.wasm", parent_pkg, name)
} else {
let pkg_folder = format!("{}/pkg/", target_path);
let _ = run_command(Command::new("mkdir").args(["-p", &pkg_folder]));
format!("{}/{}.wasm", pkg_folder, name)
};
// Embed "wit" into the component
run_command(Command::new("wasm-tools").args([
"component",
"embed",
"wit",
"--world",
"process",
&format!(
"{}/target/wasm32-wasi/release/{}_adapted.wasm",
target_path, name
),
"-o",
&wasm_dest_path,
]))
.unwrap();
let end = std::time::Instant::now();
println!(
"cargo:warning=building {} took {:?}",
target_path,
end.duration_since(start)
);
}
fn main() {
if std::env::var("SKIP_BUILD_SCRIPT").is_ok() {
println!("Skipping build script");
return;
}
let pwd = std::env::current_dir().unwrap();
// Pull wit from git repo
let wit_dir = pwd.join("wit");
fs::create_dir_all(&wit_dir).unwrap();
let wit_file = wit_dir.join("kinode.wit");
if !wit_file.exists() {
// TODO: cache in better way
let mut wit_file = std::fs::File::create(&wit_file).unwrap();
let kinode_wit_url =
"https://raw.githubusercontent.com/kinode-dao/kinode-wit/master/kinode.wit";
let mut response = reqwest::blocking::get(kinode_wit_url).unwrap();
io::copy(&mut response, &mut wit_file).unwrap();
}
// Create target.wasm (compiled .wit) & world
run_command(Command::new("wasm-tools").args([
"component",
"wit",
&format!("{}/wit/", pwd.display()),
"-o",
"target.wasm",
"--wasm",
]))
.unwrap();
run_command(Command::new("touch").args([&format!("{}/world", pwd.display())])).unwrap();
// Build wasm32-wasi apps, zip, and add to bootstrapped_processes.rs
let mut bootstrapped_processes =
fs::File::create(format!("{}/src/bootstrapped_processes.rs", pwd.display(),)).unwrap();
writeln!(
bootstrapped_processes,
"pub static BOOTSTRAPPED_PROCESSES: &[(&str, &[u8])] = &[",
)
.unwrap();
let modules_dir = format!("{}/modules", pwd.display());
for entry in std::fs::read_dir(modules_dir).unwrap() {
let entry_path = entry.unwrap().path();
// Build the app
let parent_pkg_path = format!("{}/pkg", entry_path.display());
fs::create_dir_all(&parent_pkg_path).unwrap();
// Otherwise, consider it a directory containing subdirectories with potential apps
for sub_entry in std::fs::read_dir(&entry_path).unwrap() {
let sub_entry_path = sub_entry.unwrap().path();
if sub_entry_path.join("Cargo.toml").exists() {
build_app(
&sub_entry_path.display().to_string(),
sub_entry_path.file_name().unwrap().to_str().unwrap(),
Some(&parent_pkg_path),
);
}
}
// After processing all sub-apps, zip the parent's pkg/ directory
let zip_filename = format!("{}.zip", entry_path.file_name().unwrap().to_str().unwrap(),);
let zip_path = format!("{}/target/{}", pwd.display(), zip_filename,);
let writer = std::fs::File::create(&zip_path).unwrap();
let options = zip::write::FileOptions::default()
.compression_method(zip::CompressionMethod::Stored)
.unix_permissions(0o755);
let mut zip = zip::ZipWriter::new(writer);
for sub_entry in walkdir::WalkDir::new(&parent_pkg_path) {
let sub_entry = sub_entry.unwrap();
let path = sub_entry.path();
let name = path
.strip_prefix(std::path::Path::new(&parent_pkg_path))
.unwrap();
// Write a directory or file to the ZIP archive
if path.is_file() {
zip.start_file(name.to_string_lossy().into_owned(), options)
.unwrap();
let mut file = std::fs::File::open(path).unwrap();
let mut buffer = Vec::new();
file.read_to_end(&mut buffer).unwrap();
zip.write_all(&buffer).unwrap();
} else if !name.as_os_str().is_empty() {
zip.add_directory(name.to_string_lossy().into_owned(), options)
.unwrap();
}
}
zip.finish().unwrap();
// Add zip bytes to bootstrapped_processes.rs
writeln!(
bootstrapped_processes,
" (\"{}\", include_bytes!(\"{}\")),",
zip_filename, zip_path,
)
.unwrap();
}
writeln!(bootstrapped_processes, "];").unwrap();
}

6161
kinode/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

83
kinode/Cargo.toml Normal file
View File

@ -0,0 +1,83 @@
[package]
name = "kinode"
authors = ["KinodeDAO"]
version = "0.5.3"
edition = "2021"
description = "A general-purpose sovereign cloud computing platform"
homepage = "https://kinode.org"
repository = "https://github.com/kinode-dao/kinode"
license = "Apache-2.0"
[[bin]]
name = "kinode"
path = "src/main.rs"
[build-dependencies]
anyhow = "1.0.71"
kit = { git = "https://github.com/kinode-dao/kit", rev = "15a4681" }
reqwest = { version = "0.11.22", features = ["blocking"] }
sha2 = "0.10"
tokio = { version = "1.28", features = ["macros"] }
walkdir = "2.4"
zip = "0.6"
[features]
simulation-mode = []
[dependencies]
aes-gcm = "0.10.2"
anyhow = "1.0.71"
async-trait = "0.1.71"
base64 = "0.13"
bincode = "1.3.3"
blake3 = "1.4.1"
bytes = "1.4.0"
cap-std = "2.0.0"
chacha20poly1305 = "0.10.1"
chrono = "0.4.31"
clap = { version = "4.4", features = ["derive"] }
crossterm = { version = "0.26.1", features = ["event-stream", "bracketed-paste"] }
dashmap = "5.5.3"
digest = "0.10"
elliptic-curve = { version = "0.13.8", features = ["ecdh"] }
ethers = "2.0.13"
ethers-providers = "2.0.13"
flate2 = "1.0"
futures = "0.3"
generic-array = "0.14"
getrandom = "0.2.10"
hex = "0.4.3"
hkdf = "0.12.3"
hmac = "0.12"
http = "0.2.9"
jwt = "0.16"
lib = { path = "../lib" }
lazy_static = "1.4.0"
log = "0.4.20"
nohash-hasher = "0.2.0"
num-traits = "0.2"
open = "5.0.0"
public-ip = "0.2.2"
rand = "0.8.4"
reqwest = "0.11.18"
ring = "0.16.20"
rmp-serde = "1.1.2"
rocksdb = { version = "0.21.0", features = ["multi-threaded-cf"] }
route-recognizer = "0.3.1"
rusqlite = { version = "0.30.0", features = ["bundled"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_urlencoded = "0.7"
sha2 = "0.10"
snow = { version = "0.9.3", features = ["ring-resolver"] }
static_dir = "0.2.0"
thiserror = "1.0"
tokio = { version = "1.28", features = ["fs", "macros", "rt-multi-thread", "signal", "sync"] }
tokio-stream = "0.1.14"
tokio-tungstenite = "0.20.1"
url = "2.4.1"
uuid = { version = "1.1.2", features = ["serde", "v4"] }
warp = "0.3.5"
wasmtime = "15.0.1"
wasmtime-wasi = "15.0.1"
zip = "0.6"

90
kinode/build.rs Normal file
View File

@ -0,0 +1,90 @@
use std::{
fs,
io::{Cursor, Read, Write},
};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
if std::env::var("SKIP_BUILD_SCRIPT").is_ok() {
println!("Skipping build script");
return Ok(());
}
let pwd = std::env::current_dir().unwrap();
let parent_dir = pwd.parent().unwrap();
// Build wasm32-wasi apps, zip, and add to bootstrapped_processes.rs
let mut bootstrapped_processes = Vec::new();
writeln!(
bootstrapped_processes,
"pub static BOOTSTRAPPED_PROCESSES: &[(&str, &[u8])] = &[",
)
.unwrap();
let packages_dir = format!("{}/packages", pwd.display());
eprintln!("{packages_dir:?}");
for entry in std::fs::read_dir(packages_dir).unwrap() {
let entry_path = entry.unwrap().path();
let parent_pkg_path = format!("{}/pkg", entry_path.display());
kit::build::execute(&entry_path, false, false, false, true).await?;
// After processing all sub-apps, zip the parent's pkg/ directory
let mut writer = Cursor::new(Vec::new());
{
let options = zip::write::FileOptions::default()
.compression_method(zip::CompressionMethod::Stored)
.unix_permissions(0o755);
let mut zip = zip::ZipWriter::new(&mut writer);
for sub_entry in walkdir::WalkDir::new(&parent_pkg_path) {
let sub_entry = sub_entry.unwrap();
let path = sub_entry.path();
let name = path
.strip_prefix(std::path::Path::new(&parent_pkg_path))
.unwrap();
// Write a directory or file to the ZIP archive
if path.is_file() {
zip.start_file(name.to_string_lossy().into_owned(), options)
.unwrap();
let mut file = std::fs::File::open(path).unwrap();
let mut buffer = Vec::new();
file.read_to_end(&mut buffer).unwrap();
zip.write_all(&buffer).unwrap();
} else if !name.as_os_str().is_empty() {
zip.add_directory(name.to_string_lossy().into_owned(), options)
.unwrap();
}
}
zip.finish().unwrap();
}
let zip_contents = writer.into_inner();
let zip_filename = format!("{}.zip", entry_path.file_name().unwrap().to_str().unwrap(),);
let zip_path = format!("{}/target/{}", parent_dir.display(), zip_filename);
if !std::path::Path::new(&zip_path).exists() {
fs::write(&zip_path, zip_contents)?;
} else {
let existing_zip_contents = fs::read(&zip_path)?;
if zip_contents != existing_zip_contents {
fs::write(&zip_path, zip_contents)?;
}
}
// Add zip bytes to bootstrapped_processes.rs
writeln!(
bootstrapped_processes,
" (\"{}\", include_bytes!(\"{}\")),",
zip_filename, zip_path,
)
.unwrap();
}
writeln!(bootstrapped_processes, "];").unwrap();
let bootstrapped_processes_path = pwd.join("src/bootstrapped_processes.rs");
if bootstrapped_processes_path.exists() {
let existing_bootstrapped_processes = fs::read(&bootstrapped_processes_path)?;
if bootstrapped_processes == existing_bootstrapped_processes {
return Ok(());
}
}
fs::write(&bootstrapped_processes_path, bootstrapped_processes)?;
Ok(())
}

View File

@ -1,23 +1,25 @@
[package]
name = "ft_worker"
version = "0.2.0"
name = "app_store"
version = "0.3.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.release]
panic = "abort"
opt-level = "s"
lto = true
[dependencies]
alloy-primitives = "0.5.1"
alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy.git", rev = "3b1c310" }
alloy-sol-types = "0.5.1"
anyhow = "1.0"
bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.5-alpha" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.9-alpha", features = ["eth"] }
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sha2 = "0.10.8"
sha3 = "0.10.8"
url = "2.4.1"
urlencoding = "2.1.0"
wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "efcc759" }
zip = { version = "0.6.6", default-features = false }
[lib]
crate-type = ["cdylib"]

View File

@ -0,0 +1,140 @@
use kinode_process_lib::*;
use serde::{Deserialize, Serialize};
//
// app store API
//
/// Remote requests, those sent between instantiations of this process
/// on different nodes, take this form. Will add more here in the future
#[derive(Debug, Serialize, Deserialize)]
pub enum RemoteRequest {
/// Request a package from another node who we expect to
/// be mirroring it. If the remote node is mirroring the package,
/// they must respond with RemoteResponse::DownloadApproved,
/// at which point requester can expect an FTWorkerRequest::Receive.
Download {
package_id: PackageId,
desired_version_hash: Option<String>,
},
}
/// The response expected from sending a [`RemoteRequest`].
#[derive(Debug, Serialize, Deserialize)]
pub enum RemoteResponse {
DownloadApproved,
DownloadDenied, // TODO expand on why
Metadata,
}
/// Local requests sent to the app store take this form.
#[derive(Debug, Serialize, Deserialize)]
pub enum LocalRequest {
/// Expects a zipped package as blob, and creates a new package from it.
///
/// If requested, will return a NewPackageResponse indicating success/failure.
/// This is used for locally installing a package.
NewPackage {
package: PackageId,
/// Sets whether we will mirror this package for others
mirror: bool,
},
/// Try to download a package from a specified node.
///
/// If requested, will return a DownloadResponse indicating success/failure.
/// No blob is expected.
Download {
package: PackageId,
download_from: NodeId,
/// Sets whether we will mirror this package for others
mirror: bool,
/// Sets whether we will try to automatically update this package
/// when a new version is posted to the listings contract
auto_update: bool,
/// The version hash we're looking for. If None, will download the latest.
desired_version_hash: Option<String>,
},
/// Select a downloaded package and install it. Will only succeed if the
/// package is currently in the filesystem. If the package has *already*
/// been installed, this will kill the running package and reset it with
/// what's on disk.
///
/// If requested, will return an InstallResponse indicating success/failure.
/// No blob is expected.
Install(PackageId),
/// Select an installed package and uninstall it.
/// This will kill the processes in the **manifest** of the package,
/// but not the processes that were spawned by those processes! Take
/// care to kill those processes yourself. This will also delete the drive
/// containing the source code for this package. This does not guarantee
/// that other data created by this package will be removed from places such
/// as the key-value store.
///
/// If requested, will return an UninstallResponse indicating success/failure.
/// No blob is expected.
Uninstall(PackageId),
/// Start mirroring a package. This will fail if the package has not been downloaded.
StartMirroring(PackageId),
/// Stop mirroring a package. This will fail if the package has not been downloaded.
StopMirroring(PackageId),
/// Turn on automatic updates to a package. This will fail if the package has not been downloaded.
StartAutoUpdate(PackageId),
/// Turn off automatic updates to a package. This will fail if the package has not been downloaded.
StopAutoUpdate(PackageId),
/// This is an expensive operation! Throw away our state and rebuild from scratch.
/// Re-index the locally downloaded/installed packages AND the onchain data.
RebuildIndex,
}
/// Local responses take this form.
/// The variant of `LocalResponse` given will match the `LocalRequest` it is
/// responding to.
#[derive(Debug, Serialize, Deserialize)]
pub enum LocalResponse {
NewPackageResponse(NewPackageResponse),
DownloadResponse(DownloadResponse),
InstallResponse(InstallResponse),
UninstallResponse(UninstallResponse),
MirrorResponse(MirrorResponse),
AutoUpdateResponse(AutoUpdateResponse),
RebuiltIndex,
}
// TODO for all: expand these to elucidate why something failed
// these are locally-given responses to local requests
#[derive(Debug, Serialize, Deserialize)]
pub enum NewPackageResponse {
Success,
Failure,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum DownloadResponse {
Started,
Failure,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum InstallResponse {
Success,
Failure,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum UninstallResponse {
Success,
Failure,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum MirrorResponse {
Success,
Failure,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum AutoUpdateResponse {
Success,
Failure,
}

View File

@ -0,0 +1,381 @@
use crate::{DownloadResponse, PackageListing, PackageState, RequestedPackage, State};
use kinode_process_lib::{
http::{send_response, IncomingHttpRequest, Method, StatusCode},
print_to_terminal, Address, NodeId, PackageId,
};
use serde_json::json;
use std::collections::HashMap;
/// Actions supported over HTTP:
/// - get all downloaded apps: GET /apps
/// - get all listed apps: GET /apps/listed
/// - get some subset of listed apps, via search or filter: ?
/// - get detail about a specific downloaded app: GET /apps/:id
/// - get capabilities for a specific downloaded app: GET /apps/:id/caps
/// - get detail about a specific listed app: GET /apps/listed/:id
///
/// - download a listed app: POST /apps/listed/:id
/// - install a downloaded app: POST /apps/:id
/// - uninstall/delete a downloaded app: DELETE /apps/:id
/// - update a downloaded app: PUT /apps/:id
/// - approve capabilities for a downloaded app: POST /apps/:id/caps
/// - start mirroring a downloaded app: PUT /apps/:id/mirror
/// - stop mirroring a downloaded app: DELETE /apps/:id/mirror
/// - start auto-updating a downloaded app: PUT /apps/:id/auto-update
/// - stop auto-updating a downloaded app: DELETE /apps/:id/auto-update
pub fn handle_http_request(
our: &Address,
state: &mut State,
requested_packages: &mut HashMap<PackageId, RequestedPackage>,
req: &IncomingHttpRequest,
) -> anyhow::Result<()> {
match serve_paths(our, state, requested_packages, req) {
Ok((status_code, headers, body)) => send_response(
status_code,
Some(HashMap::from([(
String::from("Content-Type"),
String::from("application/json"),
)])),
body,
),
Err(e) => {
print_to_terminal(1, &format!("http error: {:?}", e));
send_response(StatusCode::INTERNAL_SERVER_ERROR, None, vec![])
}
}
Ok(())
}
fn gen_package_info(
id: &PackageId,
listing: Option<&PackageListing>,
state: Option<&PackageState>,
) -> serde_json::Value {
json!({
"owner": match &listing {
Some(listing) => Some(&listing.owner),
None => None,
},
"package": id.package().to_string(),
"publisher": id.publisher(),
"installed": match &state {
Some(state) => state.installed,
None => false,
},
"metadata_hash": match &listing {
Some(listing) => Some(&listing.metadata_hash),
None => None,
},
"metadata": match &listing {
Some(listing) => Some(&listing.metadata),
None => match state {
Some(state) => Some(&state.metadata),
None => None,
},
},
"state": match &state {
Some(state) => json!({
"mirrored_from": state.mirrored_from,
"our_version": state.our_version,
"caps_approved": state.caps_approved,
"mirroring": state.mirroring,
"auto_update": state.auto_update,
}),
None => json!(null),
},
})
}
fn serve_paths(
our: &Address,
state: &mut State,
requested_packages: &mut HashMap<PackageId, RequestedPackage>,
req: &IncomingHttpRequest,
) -> anyhow::Result<(StatusCode, Option<HashMap<String, String>>, Vec<u8>)> {
let path = req.path()?;
let method = req.method()?;
// TODO get rid of this workaround when we change `IncomingHttpRequest`
let bound_path: &str = if path.ends_with("auto-update") {
"/apps/:id/auto-update"
} else if path.ends_with("mirror") {
"/apps/:id/mirror"
} else if path.ends_with("caps") {
"/apps/:id/caps"
} else if path.starts_with("/apps/listed/") {
"/apps/listed/:id"
} else if &path == "/apps/listed" || &path == "/apps" {
&path
} else {
"/apps/:id"
};
// print_to_terminal(0, &format!("HTTP {method} {path} {bound_path}"));
match bound_path {
// GET all downloaded apps
"/apps" => {
if method != Method::GET {
return Ok((
StatusCode::METHOD_NOT_ALLOWED,
None,
format!("Invalid method {method} for {path}").into_bytes(),
));
}
let all: Vec<serde_json::Value> = state
.downloaded_packages
.iter()
.map(|(package_id, package_state)| {
let listing = state.get_listing(package_id);
gen_package_info(package_id, listing, Some(package_state))
})
.collect();
return Ok((StatusCode::OK, None, serde_json::to_vec(&all)?));
}
// GET all listed apps
"/apps/listed" => {
if method != Method::GET {
return Ok((
StatusCode::METHOD_NOT_ALLOWED,
None,
format!("Invalid method {method} for {path}").into_bytes(),
));
}
let all: Vec<serde_json::Value> = state
.listed_packages
.iter()
.map(|(_hash, listing)| {
let package_id = PackageId::new(&listing.name, &listing.publisher);
let state = state.downloaded_packages.get(&package_id);
gen_package_info(&package_id, Some(listing), state)
})
.collect();
return Ok((StatusCode::OK, None, serde_json::to_vec(&all)?));
}
// GET detail about a specific downloaded app
// install a downloaded app: POST
// update a downloaded app: PUT
// uninstall/delete a downloaded app: DELETE
"/apps/:id" => {
let package_id = path
.split("/")
.last()
.unwrap_or_default()
.parse::<PackageId>()?;
match method {
Method::GET => {
let Some(pkg) = state.downloaded_packages.get(&package_id) else {
return Ok((
StatusCode::NOT_FOUND,
None,
format!("App not found: {package_id}").into_bytes(),
));
};
let listing = state.get_listing(&package_id);
Ok((
StatusCode::OK,
None,
gen_package_info(&package_id, listing, Some(pkg))
.to_string()
.into_bytes(),
))
}
Method::POST => {
// install an app
crate::handle_install(our, state, &package_id)?;
Ok((StatusCode::CREATED, None, format!("Installed").into_bytes()))
}
Method::PUT => {
// update an app
// TODO
Ok((StatusCode::NO_CONTENT, None, format!("TODO").into_bytes()))
}
Method::DELETE => {
// uninstall an app
state.uninstall(&package_id)?;
Ok((
StatusCode::NO_CONTENT,
None,
format!("Uninstalled").into_bytes(),
))
}
_ => Ok((
StatusCode::METHOD_NOT_ALLOWED,
None,
format!("Invalid method {method} for {path}").into_bytes(),
)),
}
}
// GET detail about a specific listed app
// download a listed app: POST
"/apps/listed/:id" => {
let package_id = path
.split("/")
.last()
.unwrap_or_default()
.parse::<PackageId>()?;
match method {
Method::GET => {
let Some(listing) = state.get_listing(&package_id) else {
return Ok((
StatusCode::NOT_FOUND,
None,
format!("App not found: {package_id}").into_bytes(),
));
};
let downloaded = state.downloaded_packages.get(&package_id);
Ok((
StatusCode::OK,
None,
gen_package_info(&package_id, Some(listing), downloaded)
.to_string()
.into_bytes(),
))
}
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"))?;
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 mirror = false;
let auto_update = false;
let desired_version_hash = None;
match crate::start_download(
our,
requested_packages,
&package_id,
download_from,
mirror,
auto_update,
&desired_version_hash,
) {
DownloadResponse::Started => Ok((
StatusCode::CREATED,
None,
format!("Downloading").into_bytes(),
)),
DownloadResponse::Failure => Ok((
StatusCode::SERVICE_UNAVAILABLE,
None,
format!("Failed to download").into_bytes(),
)),
}
}
_ => Ok((
StatusCode::METHOD_NOT_ALLOWED,
None,
format!("Invalid method {method} for {path}").into_bytes(),
)),
}
}
// GET caps for a specific downloaded app
// approve capabilities for a downloaded app: POST
"/apps/:id/caps" => {
let package_id = path
.split("/")
.nth(2)
.unwrap_or_default()
.parse::<PackageId>()?;
match method {
// return the capabilities for that app
Method::GET => Ok(match crate::fetch_package_manifest(&package_id) {
Ok(manifest) => (StatusCode::OK, None, serde_json::to_vec(&manifest)?),
Err(_) => (
StatusCode::NOT_FOUND,
None,
format!("App manifest not found: {package_id}").into_bytes(),
),
}),
// approve the capabilities for that app
Method::POST => Ok(
match state.update_downloaded_package(&package_id, |pkg| {
pkg.caps_approved = true;
}) {
true => (StatusCode::OK, None, vec![]),
false => (
StatusCode::NOT_FOUND,
None,
format!("App not found: {package_id}").into_bytes(),
),
},
),
_ => Ok((
StatusCode::METHOD_NOT_ALLOWED,
None,
format!("Invalid method {method} for {path}").into_bytes(),
)),
}
}
// start mirroring a downloaded app: PUT
// stop mirroring a downloaded app: DELETE
"/apps/:id/mirror" => {
let package_id = path
.split("/")
.nth(2)
.unwrap_or_default()
.parse::<PackageId>()?;
match method {
// start mirroring an app
Method::PUT => {
state.start_mirroring(&package_id);
Ok((StatusCode::OK, None, vec![]))
}
// stop mirroring an app
Method::DELETE => {
state.stop_mirroring(&package_id);
Ok((StatusCode::OK, None, vec![]))
}
_ => Ok((
StatusCode::METHOD_NOT_ALLOWED,
None,
format!("Invalid method {method} for {path}").into_bytes(),
)),
}
}
// start auto-updating a downloaded app: PUT
// stop auto-updating a downloaded app: DELETE
"/apps/:id/auto-update" => {
let package_id = path
.split("/")
.nth(2)
.unwrap_or_default()
.parse::<PackageId>()?;
match method {
// start auto-updating an app
Method::PUT => {
state.start_auto_update(&package_id);
Ok((StatusCode::OK, None, vec![]))
}
// stop auto-updating an app
Method::DELETE => {
state.stop_auto_update(&package_id);
Ok((StatusCode::OK, None, vec![]))
}
_ => Ok((
StatusCode::METHOD_NOT_ALLOWED,
None,
format!("Invalid method {method} for {path}").into_bytes(),
)),
}
}
_ => Ok((
StatusCode::NOT_FOUND,
None,
format!("Path not found: {}", path).into_bytes(),
)),
}
}

View File

@ -0,0 +1,706 @@
use kinode_process_lib::eth::{EthAction, EthAddress, EthSubEvent, SubscribeLogsRequest};
use kinode_process_lib::http::{bind_http_path, serve_ui, HttpServerRequest};
use kinode_process_lib::kernel_types as kt;
use kinode_process_lib::*;
use kinode_process_lib::{call_init, println};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::str::FromStr;
wit_bindgen::generate!({
path: "wit",
world: "process",
exports: {
world: Component,
},
});
mod api;
mod http_api;
use api::*;
mod types;
use types::*;
mod ft_worker_lib;
use ft_worker_lib::{
spawn_receive_transfer, spawn_transfer, FTWorkerCommand, FTWorkerResult, FileTransferContext,
};
/// App Store:
/// acts as both a local package manager and a protocol to share packages across the network.
/// packages are apps; apps are packages. we use an onchain app listing contract to determine
/// what apps are available to download and what node(s) to download them from.
///
/// once we know that list, we can request a package from a node and download it locally.
/// (we can also manually download an "untracked" package if we know its name and distributor node)
/// packages that are downloaded can then be installed!
///
/// installed packages can be managed:
/// - given permissions (necessary to complete install)
/// - uninstalled + deleted
/// - set to automatically update if a new version is available
const CONTRACT_ADDRESS: &str = "0x18c39eB547A0060C6034f8bEaFB947D1C16eADF1";
const EVENTS: [&str; 3] = [
"AppRegistered(uint256,string,bytes,string,bytes32)",
"AppMetadataUpdated(uint256,string,bytes32)",
"Transfer(address,address,uint256)",
];
// internal types
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)] // untagged as a meta-type for all incoming requests
pub enum Req {
LocalRequest(LocalRequest),
RemoteRequest(RemoteRequest),
FTWorkerCommand(FTWorkerCommand),
FTWorkerResult(FTWorkerResult),
Eth(EthSubEvent),
Http(HttpServerRequest),
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)] // untagged as a meta-type for all incoming responses
pub enum Resp {
LocalResponse(LocalResponse),
RemoteResponse(RemoteResponse),
FTWorkerResult(FTWorkerResult),
}
call_init!(init);
fn init(our: Address) {
println!("{}: started", our.package());
for path in [
"/apps",
"/apps/listed",
"/apps/:id",
"/apps/listed/:id",
"/apps/:id/caps",
"/apps/:id/mirror",
"/apps/:id/auto-update",
] {
bind_http_path(path, true, false).expect("failed to bind http path");
}
serve_ui(
&our,
"ui",
true,
false,
vec!["/", "/my-apps", "/app-details/:id", "/publish"],
)
.expect("failed to serve static UI");
// load in our saved state or initalize a new one if none exists
let mut state = get_typed_state(|bytes| Ok(bincode::deserialize(bytes)?))
.unwrap_or(State::new(CONTRACT_ADDRESS.to_string()).unwrap());
if state.contract_address != CONTRACT_ADDRESS {
println!("app store: warning: contract address mismatch--overwriting saved state");
state = State::new(CONTRACT_ADDRESS.to_string()).unwrap();
}
println!(
"app store: indexing on contract address {}",
state.contract_address
);
let mut requested_packages: HashMap<PackageId, RequestedPackage> = HashMap::new();
// subscribe to events on the app store contract
SubscribeLogsRequest::new(1) // subscription id 1
.address(EthAddress::from_str(&state.contract_address).unwrap())
.from_block(state.last_saved_block - 1)
.events(EVENTS)
.send()
.unwrap();
loop {
match await_message() {
Err(send_error) => {
// TODO handle these based on what they are triggered by
println!("app store: got network error: {send_error}");
}
Ok(message) => {
if let Err(e) = handle_message(&our, &mut state, &mut requested_packages, &message)
{
println!("app store: error handling message: {:?}", e)
}
}
}
}
}
/// message router: parse into our Req and Resp types, then pass to
/// function defined for each kind of message. check whether the source
/// of the message is allowed to send that kind of message to us.
/// finally, fire a response if expected from a request.
fn handle_message(
our: &Address,
mut state: &mut State,
mut requested_packages: &mut HashMap<PackageId, RequestedPackage>,
message: &Message,
) -> anyhow::Result<()> {
match message {
Message::Request {
source,
expects_response,
body,
..
} => match serde_json::from_slice::<Req>(&body)? {
Req::LocalRequest(local_request) => {
if our.node != source.node {
return Err(anyhow::anyhow!("local request from non-local node"));
}
let resp =
handle_local_request(&our, &local_request, &mut state, &mut requested_packages);
if expects_response.is_some() {
Response::new().body(serde_json::to_vec(&resp)?).send()?;
}
}
Req::RemoteRequest(remote_request) => {
let resp = handle_remote_request(&our, &source, &remote_request, &mut state);
if expects_response.is_some() {
Response::new().body(serde_json::to_vec(&resp)?).send()?;
}
}
Req::FTWorkerResult(FTWorkerResult::ReceiveSuccess(name)) => {
handle_receive_download(&our, &mut state, &name, &mut requested_packages)?;
}
Req::FTWorkerCommand(_) => {
spawn_receive_transfer(&our, &body)?;
}
Req::FTWorkerResult(r) => {
println!("app store: got weird ft_worker result: {r:?}");
}
Req::Eth(e) => {
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)?;
}
Req::Http(incoming) => {
if source.node() != our.node()
|| &source.process.to_string() != "http_server:distro:sys"
{
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)?;
}
}
},
Message::Response { body, context, .. } => {
// the only kind of response we care to handle here!
let Some(context) = context else {
return Err(anyhow::anyhow!("app store: missing context"));
};
handle_ft_worker_result(body, context)?;
}
}
Ok(())
}
/// so far just fielding requests to download packages from us
fn handle_remote_request(
our: &Address,
source: &Address,
request: &RemoteRequest,
state: &mut State,
) -> Resp {
match request {
RemoteRequest::Download {
package_id,
desired_version_hash,
} => {
let Some(package_state) = state.get_downloaded_package(package_id) else {
return Resp::RemoteResponse(RemoteResponse::DownloadDenied);
};
if !package_state.mirroring {
return Resp::RemoteResponse(RemoteResponse::DownloadDenied);
}
if let Some(hash) = desired_version_hash {
if &package_state.our_version != hash {
return Resp::RemoteResponse(RemoteResponse::DownloadDenied);
}
}
let file_name = format!("/{}.zip", package_id);
// get the .zip from VFS and attach as blob to response
let file_path = format!("/{}/pkg/{}.zip", package_id, package_id);
let Ok(Ok(_)) = Request::to(("our", "vfs", "distro", "sys"))
.body(
serde_json::to_vec(&vfs::VfsRequest {
path: file_path,
action: vfs::VfsAction::Read,
})
.unwrap(),
)
.send_and_await_response(5)
else {
return Resp::RemoteResponse(RemoteResponse::DownloadDenied);
};
// transfer will *inherit* the blob bytes we receive from VFS
match spawn_transfer(&our, &file_name, None, 60, &source) {
Ok(()) => Resp::RemoteResponse(RemoteResponse::DownloadApproved),
Err(_e) => Resp::RemoteResponse(RemoteResponse::DownloadDenied),
}
}
}
}
/// only `our.node` can call this
fn handle_local_request(
our: &Address,
request: &LocalRequest,
state: &mut State,
requested_packages: &mut HashMap<PackageId, RequestedPackage>,
) -> LocalResponse {
match request {
LocalRequest::NewPackage { package, mirror } => {
let Some(blob) = get_blob() else {
return LocalResponse::NewPackageResponse(NewPackageResponse::Failure);
};
// set the version hash for this new local package
let our_version = generate_version_hash(&blob.bytes);
let package_state = PackageState {
mirrored_from: Some(our.node.clone()),
our_version,
installed: false,
caps_approved: true, // TODO see if we want to auto-approve local installs
mirroring: *mirror,
auto_update: false, // can't auto-update a local package
metadata: None, // TODO
};
let Ok(()) = state.add_downloaded_package(package, package_state, Some(blob.bytes))
else {
return LocalResponse::NewPackageResponse(NewPackageResponse::Failure);
};
LocalResponse::NewPackageResponse(NewPackageResponse::Success)
}
LocalRequest::Download {
package: package_id,
download_from,
mirror,
auto_update,
desired_version_hash,
} => LocalResponse::DownloadResponse(start_download(
our,
requested_packages,
package_id,
download_from,
*mirror,
*auto_update,
desired_version_hash,
)),
LocalRequest::Install(package) => match handle_install(our, state, package) {
Ok(()) => LocalResponse::InstallResponse(InstallResponse::Success),
Err(_) => LocalResponse::InstallResponse(InstallResponse::Failure),
},
LocalRequest::Uninstall(package) => match state.uninstall(package) {
Ok(()) => LocalResponse::UninstallResponse(UninstallResponse::Success),
Err(_) => LocalResponse::UninstallResponse(UninstallResponse::Failure),
},
LocalRequest::StartMirroring(package) => match state.start_mirroring(package) {
true => LocalResponse::MirrorResponse(MirrorResponse::Success),
false => LocalResponse::MirrorResponse(MirrorResponse::Failure),
},
LocalRequest::StopMirroring(package) => match state.stop_mirroring(package) {
true => LocalResponse::MirrorResponse(MirrorResponse::Success),
false => LocalResponse::MirrorResponse(MirrorResponse::Failure),
},
LocalRequest::StartAutoUpdate(package) => match state.start_auto_update(package) {
true => LocalResponse::AutoUpdateResponse(AutoUpdateResponse::Success),
false => LocalResponse::AutoUpdateResponse(AutoUpdateResponse::Failure),
},
LocalRequest::StopAutoUpdate(package) => match state.stop_auto_update(package) {
true => LocalResponse::AutoUpdateResponse(AutoUpdateResponse::Success),
false => LocalResponse::AutoUpdateResponse(AutoUpdateResponse::Failure),
},
LocalRequest::RebuildIndex => {
*state = State::new(CONTRACT_ADDRESS.to_string()).unwrap();
// kill our old subscription and build a new one.
Request::to(("our", "eth", "distro", "sys"))
.body(serde_json::to_vec(&EthAction::UnsubscribeLogs(1)).unwrap())
.send()
.unwrap();
SubscribeLogsRequest::new(1) // subscription id 1
.address(EthAddress::from_str(&state.contract_address).unwrap())
.from_block(state.last_saved_block - 1)
.events(EVENTS)
.send()
.unwrap();
LocalResponse::RebuiltIndex
}
}
}
pub fn start_download(
our: &Address,
requested_packages: &mut HashMap<PackageId, RequestedPackage>,
package_id: &PackageId,
download_from: &NodeId,
mirror: bool,
auto_update: bool,
desired_version_hash: &Option<String>,
) -> DownloadResponse {
match Request::to((download_from.as_str(), our.process.clone()))
.inherit(true)
.body(
serde_json::to_vec(&RemoteRequest::Download {
package_id: package_id.clone(),
desired_version_hash: desired_version_hash.clone(),
})
.unwrap(),
)
.send_and_await_response(5)
{
Ok(Ok(Message::Response { body, .. })) => match serde_json::from_slice::<Resp>(&body) {
Ok(Resp::RemoteResponse(RemoteResponse::DownloadApproved)) => {
requested_packages.insert(
package_id.clone(),
RequestedPackage {
from: download_from.clone(),
mirror,
auto_update,
desired_version_hash: desired_version_hash.clone(),
},
);
DownloadResponse::Started
}
_ => DownloadResponse::Failure,
},
_ => DownloadResponse::Failure,
}
}
fn handle_receive_download(
our: &Address,
state: &mut State,
package_name: &str,
requested_packages: &mut HashMap<PackageId, RequestedPackage>,
) -> anyhow::Result<()> {
// remove leading / and .zip from file name to get package ID
let package_name = package_name[1..].trim_end_matches(".zip");
let Ok(package_id) = package_name.parse::<PackageId>() else {
return Err(anyhow::anyhow!(
"app store: bad package filename fron download: {package_name}"
));
};
println!("app store: successfully received {}", package_id);
// only save the package if we actually requested it
let Some(requested_package) = requested_packages.remove(&package_id) else {
return Err(anyhow::anyhow!(
"app store: received unrequested package--rejecting!"
));
};
let Some(blob) = get_blob() else {
return Err(anyhow::anyhow!(
"app store: received download but found no blob"
));
};
// check the version hash for this download against requested!!
// for now we can reject if it's not latest.
let download_hash = generate_version_hash(&blob.bytes);
match requested_package.desired_version_hash {
Some(hash) => {
if download_hash != hash {
return Err(anyhow::anyhow!(
"app store: downloaded package is not latest version--rejecting download!"
));
}
}
None => {
// check against latest from listing
let Some(package_listing) = state.get_listing(&package_id) else {
return Err(anyhow::anyhow!(
"app store: downloaded package cannot be found in manager--rejecting download!"
));
};
if let Some(metadata) = &package_listing.metadata {
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!"
));
}
} else {
return Err(anyhow::anyhow!(
"app store: downloaded package has no versions in manager--rejecting download!"
));
}
} else {
println!("app store: warning: downloaded package has no listing metadata to check validity against!")
}
}
}
state.add_downloaded_package(
&package_id,
PackageState {
mirrored_from: Some(requested_package.from),
our_version: download_hash,
installed: false,
caps_approved: false,
mirroring: requested_package.mirror,
auto_update: requested_package.auto_update,
metadata: None, // TODO
},
Some(blob.bytes),
)
}
fn handle_ft_worker_result(body: &[u8], context: &[u8]) -> anyhow::Result<()> {
if let Ok(Resp::FTWorkerResult(ft_worker_result)) = serde_json::from_slice::<Resp>(body) {
let context = serde_json::from_slice::<FileTransferContext>(context)?;
if let FTWorkerResult::SendSuccess = ft_worker_result {
println!(
"app store: successfully shared {} in {:.4}s",
context.file_name,
std::time::SystemTime::now()
.duration_since(context.start_time)
.unwrap()
.as_secs_f64(),
);
} else {
return Err(anyhow::anyhow!("app store: failed to share app"));
}
}
Ok(())
}
fn handle_eth_sub_event(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)
}
fn fetch_package_manifest(package: &PackageId) -> anyhow::Result<Vec<kt::PackageManifestEntry>> {
let drive_path = format!("/{}/pkg", package);
Request::to(("our", "vfs", "distro", "sys"))
.body(serde_json::to_vec(&vfs::VfsRequest {
path: format!("{}/manifest.json", drive_path),
action: vfs::VfsAction::Read,
})?)
.send_and_await_response(5)??;
let Some(blob) = get_blob() else {
return Err(anyhow::anyhow!("no blob"));
};
let manifest = String::from_utf8(blob.bytes)?;
Ok(serde_json::from_str::<Vec<kt::PackageManifestEntry>>(
&manifest,
)?)
}
/// the steps to take an existing package on disk and install/start it
/// make sure you have reviewed and approved caps in manifest before calling this
pub fn handle_install(
our: &Address,
state: &mut State,
package_id: &PackageId,
) -> anyhow::Result<()> {
let drive_path = format!("/{package_id}/pkg");
let manifest = fetch_package_manifest(package_id)?;
// always grant read/write to their drive, which we created for them
let Some(read_cap) = get_capability(
&Address::new(&our.node, ("vfs", "distro", "sys")),
&serde_json::to_string(&serde_json::json!({
"kind": "read",
"drive": drive_path,
}))?,
) else {
return Err(anyhow::anyhow!("app store: no read cap"));
};
let Some(write_cap) = get_capability(
&Address::new(&our.node, ("vfs", "distro", "sys")),
&serde_json::to_string(&serde_json::json!({
"kind": "write",
"drive": drive_path,
}))?,
) else {
return Err(anyhow::anyhow!("app store: no write cap"));
};
let Some(networking_cap) = get_capability(
&Address::new(&our.node, ("kernel", "distro", "sys")),
&"\"network\"".to_string(),
) else {
return Err(anyhow::anyhow!("app store: no net cap"));
};
// first, for each process in manifest, initialize it
// then, once all have been initialized, grant them requested caps
// and finally start them.
for entry in &manifest {
let wasm_path = if entry.process_wasm_path.starts_with("/") {
entry.process_wasm_path.clone()
} else {
format!("/{}", entry.process_wasm_path)
};
let wasm_path = format!("{}{}", drive_path, wasm_path);
let process_id = format!("{}:{}", entry.process_name, package_id);
let Ok(parsed_new_process_id) = process_id.parse::<ProcessId>() else {
return Err(anyhow::anyhow!("app store: invalid process id!"));
};
// kill process if it already exists
Request::to(("our", "kernel", "distro", "sys"))
.body(serde_json::to_vec(&kt::KernelCommand::KillProcess(
parsed_new_process_id.clone(),
))?)
.send()?;
let _bytes_response = Request::to(("our", "vfs", "distro", "sys"))
.body(serde_json::to_vec(&vfs::VfsRequest {
path: wasm_path.clone(),
action: vfs::VfsAction::Read,
})?)
.send_and_await_response(5)??;
Request::new()
.target(("our", "kernel", "distro", "sys"))
.body(serde_json::to_vec(&kt::KernelCommand::InitializeProcess {
id: parsed_new_process_id.clone(),
wasm_bytes_handle: wasm_path,
wit_version: None,
on_exit: entry.on_exit.clone(),
initial_capabilities: HashSet::new(),
public: entry.public,
})?)
.inherit(true)
.send_and_await_response(5)??;
// build initial caps
let mut requested_capabilities: Vec<kt::Capability> = vec![];
for value in &entry.request_capabilities {
match value {
serde_json::Value::String(process_name) => {
if let Ok(parsed_process_id) = process_name.parse::<ProcessId>() {
requested_capabilities.push(kt::Capability {
issuer: Address {
node: our.node.clone(),
process: parsed_process_id.clone(),
},
params: "\"messaging\"".into(),
});
} else {
println!(
"app-store: invalid cap: {} for {} to request!",
value.to_string(),
package_id
);
}
}
serde_json::Value::Object(map) => {
if let Some(process_name) = map.get("process") {
if let Ok(parsed_process_id) = process_name
.as_str()
.unwrap_or_default()
.parse::<ProcessId>()
{
if let Some(params) = map.get("params") {
requested_capabilities.push(kt::Capability {
issuer: Address {
node: our.node.clone(),
process: parsed_process_id.clone(),
},
params: params.to_string(),
});
} else {
println!(
"app-store: invalid cap: {} for {} to request!",
value.to_string(),
package_id
);
}
}
}
}
_ => {
continue;
}
}
}
if entry.request_networking {
requested_capabilities.push(kt::de_wit_capability(networking_cap.clone()));
}
requested_capabilities.push(kt::de_wit_capability(read_cap.clone()));
requested_capabilities.push(kt::de_wit_capability(write_cap.clone()));
Request::new()
.target(("our", "kernel", "distro", "sys"))
.body(serde_json::to_vec(&kt::KernelCommand::GrantCapabilities {
target: parsed_new_process_id.clone(),
capabilities: requested_capabilities,
})?)
.send()?;
}
// THEN, *after* all processes have been initialized, grant caps in manifest
// TODO for both grants and requests: make the vector of caps
// and then do one GrantCapabilities message at the end. much faster.
for entry in &manifest {
let process_id = format!("{}:{}", entry.process_name, package_id);
let Ok(parsed_new_process_id) = process_id.parse::<ProcessId>() else {
return Err(anyhow::anyhow!("app store: invalid process id!"));
};
for value in &entry.grant_capabilities {
match value {
serde_json::Value::String(process_name) => {
if let Ok(parsed_process_id) = process_name.parse::<ProcessId>() {
Request::to(("our", "kernel", "distro", "sys"))
.body(
serde_json::to_vec(&kt::KernelCommand::GrantCapabilities {
target: parsed_process_id,
capabilities: vec![kt::Capability {
issuer: Address {
node: our.node.clone(),
process: parsed_new_process_id.clone(),
},
params: "\"messaging\"".into(),
}],
})
.unwrap(),
)
.send()?;
}
}
serde_json::Value::Object(map) => {
if let Some(process_name) = map.get("process") {
if let Ok(parsed_process_id) = process_name
.as_str()
.unwrap_or_default()
.parse::<ProcessId>()
{
if let Some(params) = map.get("params") {
Request::to(("our", "kernel", "distro", "sys"))
.body(serde_json::to_vec(
&kt::KernelCommand::GrantCapabilities {
target: parsed_process_id,
capabilities: vec![kt::Capability {
issuer: Address {
node: our.node.clone(),
process: parsed_new_process_id.clone(),
},
params: params.to_string(),
}],
},
)?)
.send()?;
}
}
}
}
_ => {
continue;
}
}
}
Request::to(("our", "kernel", "distro", "sys"))
.body(serde_json::to_vec(&kt::KernelCommand::RunProcess(
parsed_new_process_id,
))?)
.send_and_await_response(5)??;
}
// finally set the package as installed
state.update_downloaded_package(package_id, |package_state| {
package_state.installed = true;
});
Ok(())
}

View File

@ -0,0 +1,585 @@
use alloy_rpc_types::Log;
use alloy_sol_types::{sol, SolEvent};
use kinode_process_lib::kernel_types as kt;
use kinode_process_lib::{println, *};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
sol! {
event AppRegistered(
uint256 indexed package,
string packageName,
bytes publisherName,
string metadataUrl,
bytes32 metadataHash
);
event AppMetadataUpdated(
uint256 indexed package,
string metadataUrl,
bytes32 metadataHash
);
event Transfer(
address indexed from,
address indexed to,
uint256 indexed tokenId
);
}
/// from kns_indexer:sys
#[derive(Debug, Serialize, Deserialize)]
pub enum IndexerRequests {
/// return the human readable name for a namehash
/// returns an Option<String>
NamehashToName { hash: String, block: u64 },
/// return the most recent on-chain routing information for a node name.
/// returns an Option<KnsUpdate>
NodeInfo { name: String, block: u64 },
}
//
// app store types
//
pub type PackageHash = String;
/// listing information derived from metadata hash in listing event
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PackageListing {
pub owner: String, // eth address
pub name: String,
pub publisher: NodeId,
pub metadata_hash: String,
pub metadata: Option<OnchainPackageMetadata>,
}
/// metadata derived from metadata hash in listing event
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OnchainPackageMetadata {
pub name: Option<String>,
pub subtitle: Option<String>,
pub description: Option<String>,
pub image: Option<String>,
pub version: Option<String>,
pub license: Option<String>,
pub website: Option<String>,
pub screenshots: Option<Vec<String>>,
pub mirrors: Option<Vec<NodeId>>,
pub versions: Option<Vec<String>>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct RequestedPackage {
pub from: NodeId,
pub mirror: bool,
pub auto_update: bool,
// if none, we're requesting the latest version onchain
pub desired_version_hash: Option<String>,
}
/// state of an individual package we have downloaded
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PackageState {
/// the node we last downloaded the package from
/// this is "us" if we don't know the source (usually cause it's a local install)
pub mirrored_from: Option<NodeId>,
/// the version of the package we have downloaded
pub our_version: String,
pub installed: bool,
pub caps_approved: bool,
/// are we serving this package to others?
pub mirroring: bool,
/// if we get a listing data update, will we try to download it?
pub auto_update: bool,
pub metadata: Option<OnchainPackageMetadata>,
}
/// this process's saved state
#[derive(Debug, Serialize, Deserialize)]
pub struct State {
/// the address of the contract we are using to read package listings
pub contract_address: String,
/// the last block at which we saved the state of the listings to disk.
/// we don't want to save the state every time we get a new listing,
/// so we only save it every so often and then mark the block at which
/// that last occurred here. when we boot, we can read logs starting
/// from this block and rebuild latest state.
pub last_saved_block: u64,
/// we keep the full state of the package manager here, calculated from
/// the listings contract logs. in the future, we'll offload this and
/// only track a certain number of packages...
pub package_hashes: HashMap<PackageId, PackageHash>, // TODO migrate to sqlite db
pub listed_packages: HashMap<PackageHash, PackageListing>, // TODO migrate to sqlite db
/// we keep the full state of the packages we have downloaded here.
/// in order to keep this synchronized with our filesystem, we will
/// ingest apps on disk if we have to rebuild our state. this is also
/// updated every time we download, create, or uninstall a package.
pub downloaded_packages: HashMap<PackageId, PackageState>, // TODO migrate to sqlite db
}
impl State {
/// To create a new state, we populate the downloaded_packages map
/// with all packages parseable from our filesystem.
pub fn new(contract_address: String) -> anyhow::Result<Self> {
crate::print_to_terminal(1, "app store: producing new state");
let mut state = State {
contract_address,
last_saved_block: 1,
package_hashes: HashMap::new(),
listed_packages: HashMap::new(),
downloaded_packages: HashMap::new(),
};
crate::print_to_terminal(
1,
&format!("populate: {:?}", state.populate_packages_from_filesystem()),
);
Ok(state)
}
pub fn get_listing(&self, package_id: &PackageId) -> Option<&PackageListing> {
self.listed_packages
.get(self.package_hashes.get(package_id)?)
}
fn get_listing_with_hash_mut(
&mut self,
package_hash: &PackageHash,
) -> Option<&mut PackageListing> {
self.listed_packages.get_mut(package_hash)
}
/// Done in response to any new onchain listing update other than 'delete'
fn insert_listing(&mut self, package_hash: PackageHash, listing: PackageListing) {
self.package_hashes.insert(
PackageId::new(&listing.name, &listing.publisher),
package_hash.clone(),
);
self.listed_packages.insert(package_hash, listing);
}
/// Done in response to an onchain listing update of 'delete'
fn delete_listing(&mut self, package_hash: &PackageHash) {
if let Some(old) = self.listed_packages.remove(package_hash) {
self.package_hashes
.remove(&PackageId::new(&old.name, &old.publisher));
}
}
pub fn get_downloaded_package(&self, package_id: &PackageId) -> Option<PackageState> {
self.downloaded_packages.get(package_id).cloned()
}
pub fn add_downloaded_package(
&mut self,
package_id: &PackageId,
package_state: PackageState,
package_bytes: Option<Vec<u8>>,
) -> anyhow::Result<()> {
if let Some(package_bytes) = package_bytes {
let drive_name = format!("/{package_id}/pkg");
let blob = LazyLoadBlob {
mime: Some("application/zip".to_string()),
bytes: package_bytes,
};
// create a new drive for this package in VFS
// this is possible because we have root access
Request::to(("our", "vfs", "distro", "sys"))
.body(serde_json::to_vec(&vfs::VfsRequest {
path: drive_name.clone(),
action: vfs::VfsAction::CreateDrive,
})?)
.send_and_await_response(5)??;
// convert the zip to a new package drive
let response = Request::to(("our", "vfs", "distro", "sys"))
.body(serde_json::to_vec(&vfs::VfsRequest {
path: drive_name.clone(),
action: vfs::VfsAction::AddZip,
})?)
.blob(blob.clone())
.send_and_await_response(5)??;
let vfs::VfsResponse::Ok = serde_json::from_slice::<vfs::VfsResponse>(response.body())?
else {
return Err(anyhow::anyhow!(
"cannot add NewPackage: do not have capability to access vfs"
));
};
// save the zip file itself in VFS for sharing with other nodes
// call it <package_id>.zip
let zip_path = format!("{}/{}.zip", drive_name, package_id);
Request::to(("our", "vfs", "distro", "sys"))
.body(serde_json::to_vec(&vfs::VfsRequest {
path: zip_path,
action: vfs::VfsAction::Write,
})?)
.blob(blob)
.send_and_await_response(5)??;
}
self.downloaded_packages
.insert(package_id.to_owned(), package_state);
crate::set_state(&bincode::serialize(self)?);
Ok(())
}
/// returns True if the package was found and updated, False otherwise
pub fn update_downloaded_package(
&mut self,
package_id: &PackageId,
fn_: impl FnOnce(&mut PackageState),
) -> bool {
let res = self
.downloaded_packages
.get_mut(package_id)
.map(|package_state| {
fn_(package_state);
true
})
.unwrap_or(false);
crate::set_state(&bincode::serialize(self).unwrap());
res
}
pub fn start_mirroring(&mut self, package_id: &PackageId) -> bool {
self.update_downloaded_package(package_id, |package_state| {
package_state.mirroring = true;
})
}
pub fn stop_mirroring(&mut self, package_id: &PackageId) -> bool {
self.update_downloaded_package(package_id, |package_state| {
package_state.mirroring = false;
})
}
pub fn start_auto_update(&mut self, package_id: &PackageId) -> bool {
self.update_downloaded_package(package_id, |package_state| {
package_state.auto_update = true;
})
}
pub fn stop_auto_update(&mut self, package_id: &PackageId) -> bool {
self.update_downloaded_package(package_id, |package_state| {
package_state.auto_update = false;
})
}
/// saves state
pub fn populate_packages_from_filesystem(&mut self) -> anyhow::Result<()> {
let Message::Response { body, .. } = Request::to(("our", "vfs", "distro", "sys"))
.body(serde_json::to_vec(&vfs::VfsRequest {
path: "/".to_string(),
action: vfs::VfsAction::ReadDir,
})?)
.send_and_await_response(3)??
else {
return Err(anyhow::anyhow!("vfs: bad response"));
};
let response = serde_json::from_slice::<vfs::VfsResponse>(&body)?;
crate::print_to_terminal(1, &format!("vfs response: {:?}", response));
let vfs::VfsResponse::ReadDir(entries) = response else {
return Err(anyhow::anyhow!("vfs: unexpected response: {:?}", response));
};
for entry in entries {
crate::print_to_terminal(1, &format!("entry: {:?}", entry));
// ignore non-package dirs
let Ok(package_id) = entry.path.parse::<PackageId>() else {
continue;
};
if entry.file_type == vfs::FileType::Directory {
let zip_file = vfs::File {
path: format!("/{}/pkg/{}.zip", package_id, package_id),
};
let Ok(zip_file_bytes) = zip_file.read() else {
continue;
};
// 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);
// 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.
self.add_downloaded_package(
&package_id,
PackageState {
mirrored_from: None,
our_version,
installed: true,
caps_approved: true, // since it's already installed this must be true
mirroring: false,
auto_update: false,
metadata: None,
},
None,
)?
}
}
Ok(())
}
pub fn uninstall(&mut self, package_id: &PackageId) -> anyhow::Result<()> {
let drive_path = format!("/{package_id}/pkg");
Request::new()
.target(("our", "vfs", "distro", "sys"))
.body(serde_json::to_vec(&vfs::VfsRequest {
path: format!("{}/manifest.json", drive_path),
action: vfs::VfsAction::Read,
})?)
.send_and_await_response(5)??;
let Some(blob) = get_blob() else {
return Err(anyhow::anyhow!("no blob"));
};
let manifest = String::from_utf8(blob.bytes)?;
let manifest = serde_json::from_str::<Vec<kt::PackageManifestEntry>>(&manifest)?;
// reading from the package manifest, kill every process
for entry in &manifest {
let process_id = format!("{}:{}", entry.process_name, package_id);
let Ok(parsed_new_process_id) = process_id.parse::<ProcessId>() else {
continue;
};
Request::new()
.target(("our", "kernel", "distro", "sys"))
.body(serde_json::to_vec(&kt::KernelCommand::KillProcess(
parsed_new_process_id,
))?)
.send()?;
}
// then, delete the drive
Request::new()
.target(("our", "vfs", "distro", "sys"))
.body(serde_json::to_vec(&vfs::VfsRequest {
path: drive_path,
action: vfs::VfsAction::RemoveDirAll,
})?)
.send_and_await_response(5)??;
// finally, remove from downloaded packages
self.downloaded_packages.remove(package_id);
crate::set_state(&bincode::serialize(self)?);
println!("app store: uninstalled {package_id}");
Ok(())
}
/// 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<()> {
let block_number: u64 = log
.block_number
.ok_or(anyhow::anyhow!("app store: got log with no block number"))?
.try_into()?;
// let package_hash: alloy_primitives::U256 = log.topics[1].into();
// let package_hash = package_hash.to_string();
match log.topics[0] {
AppRegistered::SIGNATURE_HASH => {
let package_hash = log.topics[1];
let (package_name, publisher_dnswire, metadata_url, metadata_hash) =
AppRegistered::abi_decode_data(&log.data, true)?;
let package_hash = package_hash.to_string();
let metadata_hash = metadata_hash.to_string();
crate::print_to_terminal(
1,
&format!(
"app registered with publisher_dnswire {:?}, package_hash {}, package_name {}, metadata_url {}, metadata_hash {}",
publisher_dnswire, package_hash, package_name, metadata_url, metadata_hash
)
);
if generate_package_hash(&package_name, publisher_dnswire.as_slice())
!= package_hash
{
return Err(anyhow::anyhow!(
"app store: got log with mismatched package hash"
));
}
let Ok(publisher_name) = dnswire_decode(publisher_dnswire.as_slice()) else {
return Err(anyhow::anyhow!(
"app store: got log with invalid publisher name"
));
};
let metadata = fetch_metadata(&metadata_url, &metadata_hash).ok();
let listing = match self.get_listing_with_hash_mut(&package_hash) {
Some(current_listing) => {
current_listing.name = package_name;
current_listing.publisher = publisher_name;
current_listing.metadata_hash = metadata_hash;
current_listing.metadata = metadata;
current_listing.clone()
}
None => PackageListing {
owner: "".to_string(),
name: package_name,
publisher: publisher_name,
metadata_hash,
metadata,
},
};
self.insert_listing(package_hash, listing);
}
AppMetadataUpdated::SIGNATURE_HASH => {
let package_hash = log.topics[1].to_string();
let (metadata_url, metadata_hash) =
AppMetadataUpdated::abi_decode_data(&log.data, false)?;
let metadata_hash = metadata_hash.to_string();
crate::print_to_terminal(
1,
&format!(
"app metadata updated with package_hash {}, metadata_url {}, metadata_hash {}",
package_hash, metadata_url, metadata_hash
)
);
let current_listing = self
.get_listing_with_hash_mut(&package_hash.to_string())
.ok_or(anyhow::anyhow!(
"app store: got log with no matching listing"
))?;
let metadata = match fetch_metadata(&metadata_url, &metadata_hash) {
Ok(metadata) => Some(metadata),
Err(e) => {
crate::print_to_terminal(
1,
&format!("app store: failed to fetch metadata: {e:?}"),
);
None
}
};
current_listing.metadata_hash = metadata_hash;
current_listing.metadata = metadata;
}
Transfer::SIGNATURE_HASH => {
let from = alloy_primitives::Address::from_word(log.topics[1]);
let to = alloy_primitives::Address::from_word(log.topics[2]);
let package_hash = log.topics[3].to_string();
crate::print_to_terminal(
1,
&format!(
"handling transfer from {} to {} of pkghash {}",
from, to, package_hash
),
);
if from == alloy_primitives::Address::ZERO {
crate::print_to_terminal(1, "transfer from 0 address: new app listed");
match self.get_listing_with_hash_mut(&package_hash) {
Some(current_listing) => {
current_listing.owner = to.to_string();
}
None => {
let listing = PackageListing {
owner: to.to_string(),
name: "".to_string(),
publisher: "".to_string(),
metadata_hash: "".to_string(),
metadata: None,
};
self.insert_listing(package_hash, listing);
}
}
} else if to == alloy_primitives::Address::ZERO {
crate::print_to_terminal(1, "transfer to 0 address: deleting listing");
self.delete_listing(&package_hash);
} else {
crate::print_to_terminal(1, "transferring listing");
let current_listing =
self.get_listing_with_hash_mut(&package_hash)
.ok_or(anyhow::anyhow!(
"app store: got log with no matching listing"
))?;
current_listing.owner = to.to_string();
}
}
_ => {}
}
if block_number > self.last_saved_block + 1000 {
self.last_saved_block = block_number;
crate::set_state(&bincode::serialize(self)?);
}
Ok(())
}
}
/// take a DNSwire-formatted node ID from chain and convert it to a String
fn dnswire_decode(wire_format_bytes: &[u8]) -> Result<String, std::string::FromUtf8Error> {
let mut i = 0;
let mut result = Vec::new();
while i < wire_format_bytes.len() {
let len = wire_format_bytes[i] as usize;
if len == 0 {
break;
}
let end = i + len + 1;
let mut span = wire_format_bytes[i + 1..end].to_vec();
span.push('.' as u8);
result.push(span);
i = end;
}
let flat: Vec<_> = result.into_iter().flatten().collect();
let name = String::from_utf8(flat)?;
// Remove the trailing '.' if it exists (it should always exist)
if name.ends_with('.') {
Ok(name[0..name.len() - 1].to_string())
} else {
Ok(name)
}
}
/// fetch metadata from metadata_url and verify it matches metadata_hash
fn fetch_metadata(
metadata_url: &str,
metadata_hash: &str,
) -> anyhow::Result<OnchainPackageMetadata> {
let url = url::Url::parse(metadata_url)?;
let _response = http::send_request_await_response(http::Method::GET, url, None, 5, vec![])?;
let Some(body) = get_blob() else {
return Err(anyhow::anyhow!("no blob"));
};
let hash = generate_metadata_hash(&body.bytes);
if &hash == metadata_hash {
Ok(serde_json::from_slice::<OnchainPackageMetadata>(
&body.bytes,
)?)
} else {
Err(anyhow::anyhow!(
"metadata hash mismatch: got {hash}, expected {metadata_hash}"
))
}
}
/// generate a Keccak-256 hash of the metadata bytes
fn generate_metadata_hash(metadata: &[u8]) -> String {
use sha3::{Digest, Keccak256};
let mut hasher = Keccak256::new();
hasher.update(metadata);
format!("0x{:x}", hasher.finalize())
}
/// generate a Keccak-256 hash of the package name and publisher (match onchain)
fn generate_package_hash(name: &str, publisher_dnswire: &[u8]) -> String {
use sha3::{Digest, Keccak256};
let mut hasher = Keccak256::new();
hasher.update([name.as_bytes(), publisher_dnswire].concat());
let hash = hasher.finalize();
format!("0x{:x}", hash)
}
/// generate a SHA-256 hash of the zip bytes to act as a version hash
pub fn generate_version_hash(zip_bytes: &[u8]) -> String {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(zip_bytes);
format!("{:x}", hasher.finalize())
}

View File

@ -3,14 +3,10 @@ name = "download"
version = "0.1.0"
edition = "2021"
[profile.release]
panic = "abort"
opt-level = "s"
lto = true
[dependencies]
anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "329c7a8" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.9-alpha" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "efcc759" }

View File

@ -0,0 +1 @@
../../app_store/src/api.rs

View File

@ -1,36 +1,18 @@
use kinode_process_lib::{
await_next_request_body, call_init, println, Address, Message, NodeId, PackageId, Request,
};
use serde::{Deserialize, Serialize};
mod api;
use api::*;
wit_bindgen::generate!({
path: "../../../wit",
path: "wit",
world: "process",
exports: {
world: Component,
},
});
/// grabbed from main:app_store:sys
#[derive(Debug, Serialize, Deserialize)]
pub enum LocalRequest {
Download {
package: PackageId,
install_from: NodeId,
},
}
#[derive(Debug, Serialize, Deserialize)]
pub enum LocalResponse {
DownloadResponse(DownloadResponse),
}
#[derive(Debug, Serialize, Deserialize)]
pub enum DownloadResponse {
Started,
Failure,
}
call_init!(init);
fn init(our: Address) {
@ -60,7 +42,10 @@ fn init(our: Address) {
.body(
serde_json::to_vec(&LocalRequest::Download {
package: package_id.clone(),
install_from: download_from.clone(),
download_from: download_from.clone(),
mirror: true,
auto_update: true,
desired_version_hash: None,
})
.unwrap(),
)
@ -82,5 +67,9 @@ fn init(our: Address) {
LocalResponse::DownloadResponse(DownloadResponse::Failure) => {
println!("failed to download package {package_id} from {download_from}");
}
_ => {
println!("download: unexpected response from app_store..!");
return;
}
}
}

View File

@ -1,21 +1,16 @@
[package]
name = "app_store"
name = "ft_worker"
version = "0.2.0"
edition = "2021"
[profile.release]
panic = "abort"
opt-level = "s"
lto = true
[dependencies]
anyhow = "1.0"
bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.5-alpha" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.9-alpha" }
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sha2 = "0.10.8"
wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "efcc759" }
[lib]

View File

@ -6,7 +6,7 @@ mod ft_worker_lib;
use ft_worker_lib::*;
wit_bindgen::generate!({
path: "../../../wit",
path: "wit",
world: "process",
exports: {
world: Component,

View File

@ -3,14 +3,10 @@ name = "install"
version = "0.1.0"
edition = "2021"
[profile.release]
panic = "abort"
opt-level = "s"
lto = true
[dependencies]
anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "329c7a8" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.9-alpha" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "efcc759" }

View File

@ -0,0 +1 @@
../../app_store/src/api.rs

View File

@ -1,33 +1,18 @@
use kinode_process_lib::{
await_next_request_body, call_init, println, Address, Message, NodeId, PackageId, Request,
await_next_request_body, call_init, println, Address, Message, PackageId, Request,
};
use serde::{Deserialize, Serialize};
mod api;
use api::*;
wit_bindgen::generate!({
path: "../../../wit",
path: "wit",
world: "process",
exports: {
world: Component,
},
});
/// grabbed from main:app_store:sys
#[derive(Debug, Serialize, Deserialize)]
pub enum LocalRequest {
Install(PackageId),
}
#[derive(Debug, Serialize, Deserialize)]
pub enum LocalResponse {
InstallResponse(InstallResponse),
}
#[derive(Debug, Serialize, Deserialize)]
pub enum InstallResponse {
Success,
Failure,
}
call_init!(init);
fn init(our: Address) {
@ -72,5 +57,9 @@ fn init(our: Address) {
println!("failed to install package {package_id}");
println!("make sure that the package has been downloaded!")
}
_ => {
println!("install: unexpected response from app_store..!");
return;
}
}
}

View File

@ -25,7 +25,10 @@
}
],
"grant_capabilities": [
"eth:distro:sys",
"http_client:distro:sys",
"http_server:distro:sys",
"kns_indexer:kns_indexer:sys",
"terminal:terminal:sys",
"vfs:distro:sys"
],

View File

@ -2,33 +2,33 @@
"download.wasm": {
"root": false,
"public": false,
"requestNetworking": false,
"requestCapabilities": [
"request_networking": false,
"request_capabilities": [
"main:app_store:sys"
],
"grantCapabilities": [
"grant_capabilities": [
"main:app_store:sys"
]
},
"install.wasm": {
"root": false,
"public": false,
"requestNetworking": false,
"requestCapabilities": [
"request_networking": false,
"request_capabilities": [
"main:app_store:sys"
],
"grantCapabilities": [
"grant_capabilities": [
"main:app_store:sys"
]
},
"uninstall.wasm": {
"root": false,
"public": false,
"requestNetworking": false,
"requestCapabilities": [
"request_networking": false,
"request_capabilities": [
"main:app_store:sys"
],
"grantCapabilities": [
"grant_capabilities": [
"main:app_store:sys"
]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because one or more lines are too long

View File

@ -3,14 +3,10 @@ name = "uninstall"
version = "0.1.0"
edition = "2021"
[profile.release]
panic = "abort"
opt-level = "s"
lto = true
[dependencies]
anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "329c7a8" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.9-alpha" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "efcc759" }

View File

@ -0,0 +1 @@
../../app_store/src/api.rs

View File

@ -1,33 +1,18 @@
use kinode_process_lib::{
await_next_request_body, call_init, println, Address, Message, NodeId, PackageId, Request,
await_next_request_body, call_init, println, Address, Message, PackageId, Request,
};
use serde::{Deserialize, Serialize};
mod api;
use api::*;
wit_bindgen::generate!({
path: "../../../wit",
path: "wit",
world: "process",
exports: {
world: Component,
},
});
/// grabbed from main:app_store:sys
#[derive(Debug, Serialize, Deserialize)]
pub enum LocalRequest {
Uninstall(PackageId),
}
#[derive(Debug, Serialize, Deserialize)]
pub enum LocalResponse {
UninstallResponse(UninstallResponse),
}
#[derive(Debug, Serialize, Deserialize)]
pub enum UninstallResponse {
Success,
Failure,
}
call_init!(init);
fn init(our: Address) {
@ -71,5 +56,9 @@ fn init(our: Address) {
LocalResponse::UninstallResponse(UninstallResponse::Failure) => {
println!("failed to uninstall package {package_id}!");
}
_ => {
println!("uninstall: unexpected response from app_store..!");
return;
}
}
}

View File

@ -4,9 +4,9 @@ version = 3
[[package]]
name = "anyhow"
version = "1.0.75"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
[[package]]
name = "autocfg"
@ -46,9 +46,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.1"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "bytes"
@ -88,36 +88,28 @@ dependencies = [
[[package]]
name = "crossbeam-deque"
version = "0.8.3"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.15"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"autocfg 1.1.0",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.16"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
dependencies = [
"cfg-if",
]
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "either"
@ -154,9 +146,9 @@ checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "getrandom"
version = "0.2.11"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
dependencies = [
"cfg-if",
"libc",
@ -180,9 +172,9 @@ dependencies = [
[[package]]
name = "hermit-abi"
version = "0.3.3"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f"
[[package]]
name = "http"
@ -213,9 +205,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.1.0"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b"
dependencies = [
"equivalent",
"hashbrown",
@ -230,8 +222,13 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "kinode_process_lib"
version = "0.5.5"
source = "git+https://github.com/kinode-dao/process_lib?tag=v0.5.5-alpha#722f2dbfbcc4d1bf5da1fa5db137632a3cede44c"
<<<<<<< HEAD:modules/chess/chess/Cargo.lock
version = "0.5.7"
source = "git+https://github.com/kinode-dao/process_lib?tag=v0.5.9-alpha#c1ac7227951fbd8cabf6568704f0ce11e8558c8a"
=======
version = "0.5.6"
source = "git+https://github.com/kinode-dao/process_lib?rev=fccb6a0#fccb6a0c07ebda3e385bff7f76e4984b741f01c7"
>>>>>>> develop:kinode/packages/chess/chess/Cargo.lock
dependencies = [
"anyhow",
"bincode",
@ -259,9 +256,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
[[package]]
name = "libc"
version = "0.2.151"
version = "0.2.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
[[package]]
name = "log"
@ -269,15 +266,6 @@ version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "memoffset"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "mime"
version = "0.3.17"
@ -338,18 +326,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.70"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
@ -492,9 +480,9 @@ dependencies = [
[[package]]
name = "rayon"
version = "1.8.0"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051"
dependencies = [
"either",
"rayon-core",
@ -502,9 +490,9 @@ dependencies = [
[[package]]
name = "rayon-core"
version = "1.12.0"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
@ -525,32 +513,26 @@ version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.20"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
[[package]]
name = "serde"
version = "1.0.193"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.193"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [
"proc-macro2",
"quote",
@ -559,9 +541,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.108"
version = "1.0.113"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
dependencies = [
"itoa",
"ryu",
@ -570,24 +552,24 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.11.2"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "spdx"
version = "0.10.2"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b19b32ed6d899ab23174302ff105c1577e45a06b08d4fe0a9dd13ce804bbbf71"
checksum = "62bde1398b09b9f93fc2fc9b9da86e362693e999d3a54a8ac47a99a5a73f638b"
dependencies = [
"smallvec",
]
[[package]]
name = "syn"
version = "2.0.41"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
@ -596,18 +578,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.50"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.50"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
dependencies = [
"proc-macro2",
"quote",
@ -640,9 +622,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
version = "0.3.14"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416"
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
[[package]]
name = "unicode-ident"
@ -704,10 +686,19 @@ dependencies = [
]
[[package]]
name = "wasm-metadata"
version = "0.10.14"
name = "wasm-encoder"
version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d835d67708f6374937c550ad8dd1d17c616ae009e3f00d7a0ac9f7825e78c36a"
checksum = "e09bca7d6388637d27fb5edbeab11f56bfabcef8743c55ae34370e1e5030a071"
dependencies = [
"leb128",
]
[[package]]
name = "wasm-metadata"
version = "0.10.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c853d3809fc9fccf3bc0ad63f4f51d8eefad0bacf88f957aa991c1d9b88b016e"
dependencies = [
"anyhow",
"indexmap",
@ -715,8 +706,8 @@ dependencies = [
"serde_derive",
"serde_json",
"spdx",
"wasm-encoder",
"wasmparser",
"wasm-encoder 0.41.0",
"wasmparser 0.121.0",
]
[[package]]
@ -729,6 +720,17 @@ dependencies = [
"semver",
]
[[package]]
name = "wasmparser"
version = "0.121.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953cf6a7606ab31382cb1caa5ae403e77ba70c7f8e12eeda167e7040d42bfda8"
dependencies = [
"bitflags 2.4.2",
"indexmap",
"semver",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -756,7 +758,7 @@ name = "wit-bindgen"
version = "0.16.0"
source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=efcc759#efcc7592cf3277bcb9be1034e48569c6d822b322"
dependencies = [
"bitflags 2.4.1",
"bitflags 2.4.2",
"wit-bindgen-rust-macro",
]
@ -803,23 +805,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a35a2a9992898c9d27f1664001860595a4bc99d32dd3599d547412e17d7e2"
dependencies = [
"anyhow",
"bitflags 2.4.1",
"bitflags 2.4.2",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-encoder 0.38.1",
"wasm-metadata",
"wasmparser",
"wasmparser 0.118.1",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.13.0"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15df6b7b28ce94b8be39d8df5cb21a08a4f3b9f33b631aedb4aa5776f785ead3"
checksum = "df4913a2219096373fd6512adead1fb77ecdaa59d7fc517972a7d30b12f625be"
dependencies = [
"anyhow",
"id-arena",

View File

@ -3,16 +3,12 @@ name = "chess"
version = "0.2.0"
edition = "2021"
[profile.release]
panic = "abort"
opt-level = "s"
lto = true
[dependencies]
anyhow = "1.0"
base64 = "0.13"
bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.5-alpha" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.9-alpha" }
pleco = "0.5"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -96,7 +96,7 @@ fn send_ws_update(our: &Address, game: &Game, open_channels: &HashSet<u32>) -> a
// Boilerplate: generate the wasm bindings for a process
wit_bindgen::generate!({
path: "../../../wit",
path: "wit",
world: "process",
exports: {
world: Component,
@ -111,7 +111,8 @@ fn initialize(our: Address) {
println!("{}: started", our.package());
// Serve the index.html and other UI files found in pkg/ui at the root path.
http::serve_ui(&our, "ui").unwrap();
// authenticated=true, local_only=false
http::serve_ui(&our, "ui", true, false, vec!["/"]).unwrap();
// Allow HTTP requests to be made to /games; they will be handled dynamically.
http::bind_http_path("/games", true, false).unwrap();
@ -183,12 +184,12 @@ fn handle_request(our: &Address, message: &Message, state: &mut ChessState) -> a
match handle_http_request(our, state, incoming) {
Ok(()) => Ok(()),
Err(e) => {
println!("chess: error handling http request: {:?}", e);
http::send_response(
http::StatusCode::SERVICE_UNAVAILABLE,
None,
"Service Unavailable".to_string().as_bytes().to_vec(),
)
);
Err(anyhow::anyhow!("chess: error handling http request: {e:?}"))
}
}
}
@ -419,35 +420,48 @@ fn handle_http_request(
http_request: &http::IncomingHttpRequest,
) -> anyhow::Result<()> {
if http_request.path()? != "/games" {
return http::send_response(
http::send_response(
http::StatusCode::NOT_FOUND,
None,
"Not Found".to_string().as_bytes().to_vec(),
);
return Ok(());
}
match http_request.method()?.as_str() {
// on GET: give the frontend all of our active games
"GET" => http::send_response(
"GET" => Ok(http::send_response(
http::StatusCode::OK,
Some(HashMap::from([(
String::from("Content-Type"),
String::from("application/json"),
)])),
serde_json::to_vec(&state.games)?,
),
)),
// on POST: create a new game
"POST" => {
let Some(blob) = get_blob() else {
return http::send_response(http::StatusCode::BAD_REQUEST, None, vec![]);
return Ok(http::send_response(
http::StatusCode::BAD_REQUEST,
None,
vec![],
));
};
let blob_json = serde_json::from_slice::<serde_json::Value>(&blob.bytes)?;
let Some(game_id) = blob_json["id"].as_str() else {
return http::send_response(http::StatusCode::BAD_REQUEST, None, vec![]);
return Ok(http::send_response(
http::StatusCode::BAD_REQUEST,
None,
vec![],
));
};
if let Some(game) = state.games.get(game_id)
&& !game.ended
{
return http::send_response(http::StatusCode::CONFLICT, None, vec![]);
return Ok(http::send_response(
http::StatusCode::CONFLICT,
None,
vec![],
));
};
let player_white = blob_json["white"]
@ -495,34 +509,63 @@ fn handle_http_request(
String::from("application/json"),
)])),
body,
)
);
Ok(())
}
// on PUT: make a move
"PUT" => {
let Some(blob) = get_blob() else {
return http::send_response(http::StatusCode::BAD_REQUEST, None, vec![]);
return Ok(http::send_response(
http::StatusCode::BAD_REQUEST,
None,
vec![],
));
};
let blob_json = serde_json::from_slice::<serde_json::Value>(&blob.bytes)?;
let Some(game_id) = blob_json["id"].as_str() else {
return http::send_response(http::StatusCode::BAD_REQUEST, None, vec![]);
return Ok(http::send_response(
http::StatusCode::BAD_REQUEST,
None,
vec![],
));
};
let Some(game) = state.games.get_mut(game_id) else {
return http::send_response(http::StatusCode::NOT_FOUND, None, vec![]);
return Ok(http::send_response(
http::StatusCode::NOT_FOUND,
None,
vec![],
));
};
if (game.turns % 2 == 0 && game.white != our.node)
|| (game.turns % 2 == 1 && game.black != our.node)
{
return http::send_response(http::StatusCode::FORBIDDEN, None, vec![]);
return Ok(http::send_response(
http::StatusCode::FORBIDDEN,
None,
vec![],
));
} else if game.ended {
return http::send_response(http::StatusCode::CONFLICT, None, vec![]);
return Ok(http::send_response(
http::StatusCode::CONFLICT,
None,
vec![],
));
}
let Some(move_str) = blob_json["move"].as_str() else {
return http::send_response(http::StatusCode::BAD_REQUEST, None, vec![]);
return Ok(http::send_response(
http::StatusCode::BAD_REQUEST,
None,
vec![],
));
};
let mut board = Board::from_fen(&game.board).unwrap();
if !board.apply_uci_move(move_str) {
// TODO surface illegal move to player or something here
return http::send_response(http::StatusCode::BAD_REQUEST, None, vec![]);
return Ok(http::send_response(
http::StatusCode::BAD_REQUEST,
None,
vec![],
));
}
// send the move to the other player
// check if the game is over
@ -559,15 +602,24 @@ fn handle_http_request(
String::from("application/json"),
)])),
body,
)
);
Ok(())
}
// on DELETE: end the game
"DELETE" => {
let Some(game_id) = http_request.query_params().get("id") else {
return http::send_response(http::StatusCode::BAD_REQUEST, None, vec![]);
return Ok(http::send_response(
http::StatusCode::BAD_REQUEST,
None,
vec![],
));
};
let Some(game) = state.games.get_mut(game_id) else {
return http::send_response(http::StatusCode::BAD_REQUEST, None, vec![]);
return Ok(http::send_response(
http::StatusCode::BAD_REQUEST,
None,
vec![],
));
};
// send the other player an end game request
Request::new()
@ -584,9 +636,14 @@ fn handle_http_request(
String::from("application/json"),
)])),
body,
)
);
Ok(())
}
// Any other method will be rejected.
_ => http::send_response(http::StatusCode::METHOD_NOT_ALLOWED, None, vec![]),
_ => Ok(http::send_response(
http::StatusCode::METHOD_NOT_ALLOWED,
None,
vec![],
)),
}
}

View File

@ -83,9 +83,11 @@ dependencies = [
]
[[package]]
name = "hi"
name = "homepage"
version = "0.1.0"
dependencies = [
"anyhow",
"bincode",
"kinode_process_lib",
"serde",
"serde_json",
@ -121,9 +123,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.1.0"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b"
dependencies = [
"equivalent",
"hashbrown",
@ -138,8 +140,8 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "kinode_process_lib"
version = "0.5.5"
source = "git+https://github.com/kinode-dao/process_lib?rev=329c7a8#329c7a8314973c857db38c7b712318de9349eb6e"
version = "0.5.6"
source = "git+https://github.com/kinode-dao/process_lib?rev=fccb6a0#fccb6a0c07ebda3e385bff7f76e4984b741f01c7"
dependencies = [
"anyhow",
"bincode",
@ -201,9 +203,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.76"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
@ -261,18 +263,18 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
[[package]]
name = "serde"
version = "1.0.195"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.195"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [
"proc-macro2",
"quote",
@ -281,9 +283,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.111"
version = "1.0.113"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
dependencies = [
"itoa",
"ryu",
@ -292,9 +294,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.12.0"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2593d31f82ead8df961d8bd23a64c2ccf2eb5dd34b0a34bfb4dd54011c72009e"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "spdx"
@ -427,18 +429,18 @@ dependencies = [
[[package]]
name = "wasm-encoder"
version = "0.39.0"
version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "111495d6204760238512f57a9af162f45086504da332af210f2f75dd80b34f1d"
checksum = "e09bca7d6388637d27fb5edbeab11f56bfabcef8743c55ae34370e1e5030a071"
dependencies = [
"leb128",
]
[[package]]
name = "wasm-metadata"
version = "0.10.15"
version = "0.10.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "818931c85b1d197909699d36c509fa89550ccfa0d66932ba3c1726faddb4d0c7"
checksum = "c853d3809fc9fccf3bc0ad63f4f51d8eefad0bacf88f957aa991c1d9b88b016e"
dependencies = [
"anyhow",
"indexmap",
@ -446,8 +448,8 @@ dependencies = [
"serde_derive",
"serde_json",
"spdx",
"wasm-encoder 0.39.0",
"wasmparser 0.119.0",
"wasm-encoder 0.41.0",
"wasmparser 0.121.0",
]
[[package]]
@ -462,9 +464,9 @@ dependencies = [
[[package]]
name = "wasmparser"
version = "0.119.0"
version = "0.121.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c35daf77afb4f9b14016625144a391085ec2ca99ca9cc53ed291bb53ab5278d"
checksum = "953cf6a7606ab31382cb1caa5ae403e77ba70c7f8e12eeda167e7040d42bfda8"
dependencies = [
"bitflags",
"indexmap",

View File

@ -0,0 +1,19 @@
[package]
name = "homepage"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0"
bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.9-alpha" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "efcc759" }
[lib]
crate-type = ["cdylib"]
[package.metadata.component]
package = "kinode:process"

View File

@ -9,7 +9,7 @@
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
<link href='https://fonts.googleapis.com/css?family=Montserrat' rel='stylesheet'>
<link href='https://fonts.googleapis.com/css?family=Montserrat' rel='stylesheet'>
<style>
:root {
@ -197,7 +197,8 @@
<h3 id="uq-name">Welcome ${our}!</h3>
<h4>Apps:</h4>
<!-- <a id="file-transfer" href="/file-transfer">File Transfer</a> -->
<a id="app-store" href="/main:app_store:sys/">App Store</a>
<br />
<a id="chess" href="/chess:chess:sys/">Chess</a>
</div>
<script>window.ourName = window.our = '${our}'</script>
@ -215,8 +216,8 @@
document.addEventListener('DOMContentLoaded', function () {
// const fileTransferLink = document.getElementById('file-transfer')
// fileTransferLink.attributes.href.value = (window.location.pathname + fileTransferLink.attributes.href.value).replace('//', '/')
const chessLink = document.getElementById('chess')
chessLink.attributes.href.value = (window.location.pathname + chessLink.attributes.href.value).replace('//', '/')
// const chessLink = document.getElementById('chess')
// chessLink.attributes.href.value = (window.location.pathname + chessLink.attributes.href.value).replace('//', '/')
// const webSocket = new WebSocket('ws://' + window.location.host) // Handle proxying later
// webSocket.onopen = () => {

View File

@ -4,7 +4,7 @@ use kinode_process_lib::{
};
wit_bindgen::generate!({
path: "../../../wit",
path: "wit",
world: "process",
exports: {
world: Component,

View File

@ -3,12 +3,6 @@ name = "kns_indexer"
version = "0.2.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.release]
panic = "abort"
opt-level = "s"
lto = true
[dependencies]
anyhow = "1.0"
@ -17,7 +11,7 @@ alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy.git", rev = "3b1c31
alloy-sol-types = "0.5.1"
bincode = "1.3.3"
hex = "0.4.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.5-alpha", features = ["eth"] }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.9-alpha", features = ["eth"] }
rmp-serde = "1.1.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -14,7 +14,7 @@ use std::str::FromStr;
use std::string::FromUtf8Error;
wit_bindgen::generate!({
path: "../../../wit",
path: "wit",
world: "process",
exports: {
world: Component,

View File

@ -3,14 +3,10 @@ name = "alias"
version = "0.1.0"
edition = "2021"
[profile.release]
panic = "abort"
opt-level = "s"
lto = true
[dependencies]
anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "329c7a8" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.9-alpha" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "efcc759" }

View File

@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use serde_json::json;
wit_bindgen::generate!({
path: "../../../wit",
path: "wit",
world: "process",
exports: {
world: Component,

View File

@ -3,14 +3,10 @@ name = "cat"
version = "0.1.0"
edition = "2021"
[profile.release]
panic = "abort"
opt-level = "s"
lto = true
[dependencies]
anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "329c7a8" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.9-alpha" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "efcc759" }

View File

@ -3,7 +3,7 @@ use kinode_process_lib::{
};
wit_bindgen::generate!({
path: "../../../wit",
path: "wit",
world: "process",
exports: {
world: Component,

View File

@ -3,14 +3,10 @@ name = "echo"
version = "0.1.0"
edition = "2021"
[profile.release]
panic = "abort"
opt-level = "s"
lto = true
[dependencies]
anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "329c7a8" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.9-alpha" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "efcc759" }

View File

@ -1,7 +1,7 @@
use kinode_process_lib::{await_next_request_body, call_init, println, Address, Response};
wit_bindgen::generate!({
path: "../../../wit",
path: "wit",
world: "process",
exports: {
world: Component,

View File

@ -3,13 +3,9 @@ name = "hi"
version = "0.1.0"
edition = "2021"
[profile.release]
panic = "abort"
opt-level = "s"
lto = true
[dependencies]
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "329c7a8" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.9-alpha" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "efcc759" }

View File

@ -3,7 +3,7 @@ use kinode_process_lib::{
};
wit_bindgen::generate!({
path: "../../../wit",
path: "wit",
world: "process",
exports: {
world: Component,

View File

@ -3,15 +3,11 @@ name = "m"
version = "0.1.0"
edition = "2021"
[profile.release]
panic = "abort"
opt-level = "s"
lto = true
[dependencies]
anyhow = "1.0"
clap = "4.4.18"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "329c7a8" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.9-alpha" }
regex = "1.10.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -3,7 +3,7 @@ use kinode_process_lib::{await_next_request_body, call_init, println, Address, R
use regex::Regex;
wit_bindgen::generate!({
path: "../../../wit",
path: "wit",
world: "process",
exports: {
world: Component,

View File

@ -2,24 +2,24 @@
"alias.wasm": {
"root": false,
"public": false,
"requestNetworking": false,
"requestCapabilities": [
"request_networking": false,
"request_capabilities": [
"terminal:terminal:sys"
],
"grantCapabilities": []
"grant_capabilities": []
},
"echo.wasm": {
"root": false,
"public": false,
"requestNetworking": false,
"requestCapabilities": [],
"grantCapabilities": []
"request_networking": false,
"request_capabilities": [],
"grant_capabilities": []
},
"cat.wasm": {
"root": false,
"public": false,
"requestNetworking": false,
"requestCapabilities": [
"request_networking": false,
"request_capabilities": [
"vfs:distro:sys",
{
"process": "vfs:distro:sys",
@ -28,31 +28,31 @@
}
}
],
"grantCapabilities": []
"grant_capabilities": []
},
"hi.wasm": {
"root": false,
"public": false,
"requestNetworking": true,
"requestCapabilities": [
"request_networking": true,
"request_capabilities": [
"net:distro:sys"
],
"grantCapabilities": [
"grant_capabilities": [
"net:distro:sys"
]
},
"top.wasm": {
"root": false,
"public": false,
"requestNetworking": false,
"requestCapabilities": [
"request_networking": false,
"request_capabilities": [
"kernel:distro:sys"
],
"grantCapabilities": []
"grant_capabilities": []
},
"m.wasm": {
"root": true,
"public": false,
"requestNetworking": true
"public": true,
"request_networking": true
}
}

View File

@ -3,14 +3,12 @@
version = 3
[[package]]
name = "alias"
version = "0.1.0"
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"anyhow",
"kinode_process_lib",
"serde",
"serde_json",
"wit-bindgen",
"memchr",
]
[[package]]
@ -30,9 +28,9 @@ dependencies = [
[[package]]
name = "bitflags"
version = "2.4.1"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "bytes"
@ -122,9 +120,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.1.0"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b"
dependencies = [
"equivalent",
"hashbrown",
@ -139,8 +137,8 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "kinode_process_lib"
version = "0.5.5"
source = "git+https://github.com/kinode-dao/process_lib?rev=329c7a8#329c7a8314973c857db38c7b712318de9349eb6e"
version = "0.5.6"
source = "git+https://github.com/kinode-dao/process_lib?rev=fccb6a0#fccb6a0c07ebda3e385bff7f76e4984b741f01c7"
dependencies = [
"anyhow",
"bincode",
@ -172,6 +170,12 @@ version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "memchr"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "mime"
version = "0.3.17"
@ -202,9 +206,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.76"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
@ -248,6 +252,35 @@ dependencies = [
"getrandom",
]
[[package]]
name = "regex"
version = "1.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "ryu"
version = "1.0.16"
@ -262,18 +295,18 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
[[package]]
name = "serde"
version = "1.0.195"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.195"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [
"proc-macro2",
"quote",
@ -282,9 +315,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.111"
version = "1.0.113"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
dependencies = [
"itoa",
"ryu",
@ -293,9 +326,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.12.0"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2593d31f82ead8df961d8bd23a64c2ccf2eb5dd34b0a34bfb4dd54011c72009e"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "spdx"
@ -317,6 +350,20 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "terminal"
version = "0.1.0"
dependencies = [
"anyhow",
"bincode",
"kinode_process_lib",
"rand",
"regex",
"serde",
"serde_json",
"wit-bindgen",
]
[[package]]
name = "thiserror"
version = "1.0.56"
@ -363,9 +410,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
version = "0.3.14"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416"
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
[[package]]
name = "unicode-ident"
@ -428,18 +475,18 @@ dependencies = [
[[package]]
name = "wasm-encoder"
version = "0.39.0"
version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "111495d6204760238512f57a9af162f45086504da332af210f2f75dd80b34f1d"
checksum = "e09bca7d6388637d27fb5edbeab11f56bfabcef8743c55ae34370e1e5030a071"
dependencies = [
"leb128",
]
[[package]]
name = "wasm-metadata"
version = "0.10.15"
version = "0.10.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "818931c85b1d197909699d36c509fa89550ccfa0d66932ba3c1726faddb4d0c7"
checksum = "c853d3809fc9fccf3bc0ad63f4f51d8eefad0bacf88f957aa991c1d9b88b016e"
dependencies = [
"anyhow",
"indexmap",
@ -447,8 +494,8 @@ dependencies = [
"serde_derive",
"serde_json",
"spdx",
"wasm-encoder 0.39.0",
"wasmparser 0.119.0",
"wasm-encoder 0.41.0",
"wasmparser 0.121.0",
]
[[package]]
@ -463,9 +510,9 @@ dependencies = [
[[package]]
name = "wasmparser"
version = "0.119.0"
version = "0.121.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c35daf77afb4f9b14016625144a391085ec2ca99ca9cc53ed291bb53ab5278d"
checksum = "953cf6a7606ab31382cb1caa5ae403e77ba70c7f8e12eeda167e7040d42bfda8"
dependencies = [
"bitflags",
"indexmap",

View File

@ -0,0 +1,21 @@
[package]
name = "terminal"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0"
bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.9-alpha" }
rand = "0.8"
regex = "1.10.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "efcc759" }
[lib]
crate-type = ["cdylib"]
[package.metadata.component]
package = "kinode:process"

View File

@ -2,15 +2,15 @@ use anyhow::anyhow;
use kinode_process_lib::kernel_types as kt;
use kinode_process_lib::kinode::process::standard as wit;
use kinode_process_lib::{
get_blob, get_capability, get_typed_state, our_capabilities, println, set_state, vfs, Address,
Capability, PackageId, ProcessId, Request,
get_blob, get_typed_state, our_capabilities, print_to_terminal, println, set_state, vfs,
Address, Capability, PackageId, ProcessId, Request,
};
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
wit_bindgen::generate!({
path: "../../../wit",
path: "wit",
world: "process",
exports: {
world: Component,
@ -205,13 +205,6 @@ fn handle_run(
};
let wasm_path = format!("{}{}", drive_path, wasm_path);
// build initial caps
let mut initial_capabilities: HashSet<kt::Capability> = HashSet::new();
if entry.request_networking {
initial_capabilities.insert(kt::de_wit_capability(Capability {
issuer: Address::new(&our.node, ("kernel", "distro", "sys")),
params: "\"network\"".to_string(),
}));
}
let process_id = format!("{}:{}", rand::random::<u64>(), package); // all scripts are given random process IDs
let Ok(parsed_new_process_id) = process_id.parse::<ProcessId>() else {
return Err(anyhow::anyhow!("app store: invalid process id!"));
@ -224,19 +217,31 @@ fn handle_run(
action: vfs::VfsAction::Read,
})?)
.send_and_await_response(5)??;
Request::new()
.target(("our", "kernel", "distro", "sys"))
.body(serde_json::to_vec(&kt::KernelCommand::InitializeProcess {
id: parsed_new_process_id.clone(),
wasm_bytes_handle: wasm_path.clone(),
wit_version: None,
on_exit: kt::OnExit::None, // TODO this should send a message back to runner:script:sys so that it can Drop capabilities
initial_capabilities: HashSet::new(),
public: entry.public,
})?)
.inherit(true)
.send_and_await_response(5)??;
let mut requested_caps: Vec<kt::Capability> = vec![];
if let Some(to_request) = &entry.request_capabilities {
for value in to_request {
let mut capability = None;
match value {
serde_json::Value::String(process_name) => {
if let Ok(parsed_process_id) = process_name.parse::<ProcessId>() {
capability = get_capability(
&Address {
requested_caps.push(kt::Capability {
issuer: Address {
node: our.node.clone(),
process: parsed_process_id.clone(),
},
"\"messaging\"".into(),
);
params: "\"messaging\"".into(),
});
}
}
serde_json::Value::Object(map) => {
@ -247,13 +252,13 @@ fn handle_run(
.parse::<ProcessId>()
{
if let Some(params) = map.get("params") {
capability = get_capability(
&Address {
requested_caps.push(kt::Capability {
issuer: Address {
node: our.node.clone(),
process: parsed_process_id.clone(),
},
&params.to_string(),
);
params: params.to_string(),
});
}
}
}
@ -262,36 +267,44 @@ fn handle_run(
continue;
}
}
if let Some(cap) = capability {
initial_capabilities.insert(kt::de_wit_capability(cap));
} else {
println!(
"runner: no cap: {}, for {} to request!",
value.to_string(),
package
);
}
}
}
if entry.request_networking {
requested_caps.push(kt::de_wit_capability(Capability {
issuer: Address::new(&our.node, ("kernel", "distro", "sys")),
params: "\"network\"".to_string(),
}));
}
if entry.root {
for cap in our_capabilities() {
requested_caps.push(kt::de_wit_capability(cap.clone()));
}
}
print_to_terminal(
1,
&format!(
"{}: Process {{\n wasm_bytes_handle: {},\n wit_version: {},\n on_exit: {:?},\n public: {}\n capabilities: {}\n}}",
parsed_new_process_id.clone(),
wasm_path.clone(),
"None",
kt::OnExit::None,
entry.public,
{
let mut caps_string = "[".to_string();
for cap in requested_caps.iter() {
caps_string += &format!("\n {}({})", cap.issuer.to_string(), cap.params);
}
caps_string + "\n ]"
},
),
);
Request::new()
.target(("our", "kernel", "distro", "sys"))
.body(serde_json::to_vec(&kt::KernelCommand::InitializeProcess {
id: parsed_new_process_id.clone(),
wasm_bytes_handle: wasm_path,
wit_version: None,
on_exit: kt::OnExit::None, // TODO this should send a message back to runner:script:sys so that it can Drop capabilities
initial_capabilities: if entry.root {
our_capabilities()
.iter()
.map(|wit: &kinode_process_lib::Capability| kt::de_wit_capability(wit.clone()))
.collect()
} else {
initial_capabilities
},
public: entry.public,
.body(serde_json::to_vec(&kt::KernelCommand::GrantCapabilities {
target: parsed_new_process_id.clone(),
capabilities: requested_caps,
})?)
.inherit(true)
.send_and_await_response(5)??;
.send()?;
if let Some(to_grant) = &entry.grant_capabilities {
for value in to_grant {
match value {

View File

@ -3,14 +3,10 @@ name = "top"
version = "0.1.0"
edition = "2021"
[profile.release]
panic = "abort"
opt-level = "s"
lto = true
[dependencies]
anyhow = "1.0"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", rev = "329c7a8" }
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.9-alpha" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "efcc759" }

View File

@ -4,7 +4,7 @@ use kinode_process_lib::{
};
wit_bindgen::generate!({
path: "../../../wit",
path: "wit",
world: "process",
exports: {
world: Component,

View File

@ -19,9 +19,9 @@ dependencies = [
[[package]]
name = "bitflags"
version = "2.4.1"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "bytes"
@ -111,9 +111,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.1.0"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b"
dependencies = [
"equivalent",
"hashbrown",
@ -128,8 +128,8 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "kinode_process_lib"
version = "0.5.5"
source = "git+https://github.com/kinode-dao/process_lib?rev=329c7a8#329c7a8314973c857db38c7b712318de9349eb6e"
version = "0.5.6"
source = "git+https://github.com/kinode-dao/process_lib?rev=fccb6a0#fccb6a0c07ebda3e385bff7f76e4984b741f01c7"
dependencies = [
"anyhow",
"bincode",
@ -191,9 +191,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.76"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
@ -251,18 +251,18 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
[[package]]
name = "serde"
version = "1.0.195"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.195"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [
"proc-macro2",
"quote",
@ -271,9 +271,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.111"
version = "1.0.113"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
dependencies = [
"itoa",
"ryu",
@ -282,9 +282,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.12.0"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2593d31f82ead8df961d8bd23a64c2ccf2eb5dd34b0a34bfb4dd54011c72009e"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "spdx"
@ -306,6 +306,19 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "test_runner"
version = "0.1.0"
dependencies = [
"anyhow",
"bincode",
"kinode_process_lib",
"serde",
"serde_json",
"thiserror",
"wit-bindgen",
]
[[package]]
name = "thiserror"
version = "1.0.56"
@ -352,9 +365,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
version = "0.3.14"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416"
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
[[package]]
name = "unicode-ident"
@ -383,17 +396,6 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "uninstall"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib",
"serde",
"serde_json",
"wit-bindgen",
]
[[package]]
name = "url"
version = "2.5.0"
@ -428,18 +430,18 @@ dependencies = [
[[package]]
name = "wasm-encoder"
version = "0.39.0"
version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "111495d6204760238512f57a9af162f45086504da332af210f2f75dd80b34f1d"
checksum = "e09bca7d6388637d27fb5edbeab11f56bfabcef8743c55ae34370e1e5030a071"
dependencies = [
"leb128",
]
[[package]]
name = "wasm-metadata"
version = "0.10.15"
version = "0.10.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "818931c85b1d197909699d36c509fa89550ccfa0d66932ba3c1726faddb4d0c7"
checksum = "c853d3809fc9fccf3bc0ad63f4f51d8eefad0bacf88f957aa991c1d9b88b016e"
dependencies = [
"anyhow",
"indexmap",
@ -447,8 +449,8 @@ dependencies = [
"serde_derive",
"serde_json",
"spdx",
"wasm-encoder 0.39.0",
"wasmparser 0.119.0",
"wasm-encoder 0.41.0",
"wasmparser 0.121.0",
]
[[package]]
@ -463,9 +465,9 @@ dependencies = [
[[package]]
name = "wasmparser"
version = "0.119.0"
version = "0.121.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c35daf77afb4f9b14016625144a391085ec2ca99ca9cc53ed291bb53ab5278d"
checksum = "953cf6a7606ab31382cb1caa5ae403e77ba70c7f8e12eeda167e7040d42bfda8"
dependencies = [
"bitflags",
"indexmap",

View File

@ -0,0 +1,20 @@
[package]
name = "test_runner"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0"
bincode = "1.3.3"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.9-alpha" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "efcc759" }
[lib]
crate-type = ["cdylib"]
[package.metadata.component]
package = "kinode:process"

View File

@ -1,15 +1,16 @@
use std::str::FromStr;
use kinode_process_lib::{
await_message, our_capabilities, println, spawn, vfs, Address, Message, OnExit, ProcessId,
Request, Response, vfs::{DirEntry, FileType},
await_message, our_capabilities, println, spawn, vfs,
vfs::{DirEntry, FileType},
Address, Message, OnExit, ProcessId, Request, Response,
};
mod tester_types;
use tester_types as tt;
wit_bindgen::generate!({
path: "../../../wit",
path: "wit",
world: "process",
exports: {
world: Component,
@ -32,7 +33,11 @@ fn handle_message(our: &Address) -> anyhow::Result<()> {
}
Message::Request { ref body, .. } => {
match serde_json::from_slice(body)? {
tt::TesterRequest::Run { ref test_names, test_timeout, .. } => {
tt::TesterRequest::Run {
ref test_names,
test_timeout,
..
} => {
println!("test_runner: got Run");
let dir_prefix = "tester:sys/tests";
@ -75,15 +80,16 @@ fn handle_message(our: &Address) -> anyhow::Result<()> {
let caps_file_path = format!("{}/grant_capabilities.json", dir_prefix);
let caps_index = children.iter().position(|i| *i.path == *caps_file_path);
let caps_by_child: std::collections::HashMap<String, Vec<String>> = match caps_index {
None => std::collections::HashMap::new(),
Some(caps_index) => {
children.remove(caps_index);
let file = vfs::file::open_file(&caps_file_path, false)?;
let file_contents = file.read()?;
serde_json::from_slice(&file_contents)?
}
};
let caps_by_child: std::collections::HashMap<String, Vec<String>> =
match caps_index {
None => std::collections::HashMap::new(),
Some(caps_index) => {
children.remove(caps_index);
let file = vfs::file::open_file(&caps_file_path, false)?;
let file_contents = file.read()?;
serde_json::from_slice(&file_contents)?
}
};
println!("test_runner: running {:?}...", children);
@ -91,7 +97,13 @@ fn handle_message(our: &Address) -> anyhow::Result<()> {
let test_path = format!("{}/{}.wasm", dir_prefix, test_name);
let grant_caps = caps_by_child
.get(test_name)
.and_then(|caps| Some(caps.iter().map(|cap| ProcessId::from_str(cap).unwrap()).collect()))
.and_then(|caps| {
Some(
caps.iter()
.map(|cap| ProcessId::from_str(cap).unwrap())
.collect(),
)
})
.unwrap_or(vec![]);
let child_process_id = match spawn(
None,

View File

@ -19,9 +19,9 @@ dependencies = [
[[package]]
name = "bitflags"
version = "2.4.1"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "bytes"
@ -35,17 +35,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "download"
version = "0.1.0"
dependencies = [
"anyhow",
"kinode_process_lib",
"serde",
"serde_json",
"wit-bindgen",
]
[[package]]
name = "equivalent"
version = "1.0.1"
@ -122,9 +111,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.1.0"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b"
dependencies = [
"equivalent",
"hashbrown",
@ -139,8 +128,8 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "kinode_process_lib"
version = "0.5.5"
source = "git+https://github.com/kinode-dao/process_lib?rev=329c7a8#329c7a8314973c857db38c7b712318de9349eb6e"
version = "0.5.6"
source = "git+https://github.com/kinode-dao/process_lib?rev=fccb6a0#fccb6a0c07ebda3e385bff7f76e4984b741f01c7"
dependencies = [
"anyhow",
"bincode",
@ -202,9 +191,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.76"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
@ -262,18 +251,18 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
[[package]]
name = "serde"
version = "1.0.195"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.195"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [
"proc-macro2",
"quote",
@ -282,9 +271,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.111"
version = "1.0.113"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
dependencies = [
"itoa",
"ryu",
@ -293,9 +282,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.12.0"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2593d31f82ead8df961d8bd23a64c2ccf2eb5dd34b0a34bfb4dd54011c72009e"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "spdx"
@ -317,6 +306,20 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "tester"
version = "0.1.0"
dependencies = [
"anyhow",
"bincode",
"indexmap",
"kinode_process_lib",
"serde",
"serde_json",
"thiserror",
"wit-bindgen",
]
[[package]]
name = "thiserror"
version = "1.0.56"
@ -363,9 +366,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
version = "0.3.14"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416"
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
[[package]]
name = "unicode-ident"
@ -428,18 +431,18 @@ dependencies = [
[[package]]
name = "wasm-encoder"
version = "0.39.0"
version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "111495d6204760238512f57a9af162f45086504da332af210f2f75dd80b34f1d"
checksum = "e09bca7d6388637d27fb5edbeab11f56bfabcef8743c55ae34370e1e5030a071"
dependencies = [
"leb128",
]
[[package]]
name = "wasm-metadata"
version = "0.10.15"
version = "0.10.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "818931c85b1d197909699d36c509fa89550ccfa0d66932ba3c1726faddb4d0c7"
checksum = "c853d3809fc9fccf3bc0ad63f4f51d8eefad0bacf88f957aa991c1d9b88b016e"
dependencies = [
"anyhow",
"indexmap",
@ -447,8 +450,8 @@ dependencies = [
"serde_derive",
"serde_json",
"spdx",
"wasm-encoder 0.39.0",
"wasmparser 0.119.0",
"wasm-encoder 0.41.0",
"wasmparser 0.121.0",
]
[[package]]
@ -463,9 +466,9 @@ dependencies = [
[[package]]
name = "wasmparser"
version = "0.119.0"
version = "0.121.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c35daf77afb4f9b14016625144a391085ec2ca99ca9cc53ed291bb53ab5278d"
checksum = "953cf6a7606ab31382cb1caa5ae403e77ba70c7f8e12eeda167e7040d42bfda8"
dependencies = [
"bitflags",
"indexmap",

View File

@ -0,0 +1,21 @@
[package]
name = "tester"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0"
bincode = "1.3.3"
indexmap = "2.1"
kinode_process_lib = { git = "https://github.com/kinode-dao/process_lib", tag = "v0.5.9-alpha" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "efcc759" }
[lib]
crate-type = ["cdylib"]
[package.metadata.component]
package = "kinode:process"

View File

@ -10,7 +10,7 @@ mod tester_types;
use tester_types as tt;
wit_bindgen::generate!({
path: "../../../wit",
path: "wit",
world: "process",
exports: {
world: Component,

4
kinode/src/eth/mod.rs Normal file
View File

@ -0,0 +1,4 @@
#![allow(unused)]
pub mod provider;
pub use lib::types::eth as types;

View File

@ -1,5 +1,3 @@
use crate::eth::types::*;
use crate::types::*;
use anyhow::Result;
use ethers::prelude::Provider;
use ethers::types::Filter;
@ -8,6 +6,8 @@ use std::collections::HashMap;
use std::sync::Arc;
use url::Url;
use lib::types::{core::*, eth::*};
const WS_RECONNECTS: usize = 10_000; // TODO workshop this
/// The ETH provider runtime process is responsible for connecting to one or more ETH RPC providers

View File

@ -1,6 +1,3 @@
use crate::http::client_types::*;
use crate::http::server_types::*;
use crate::types::*;
use anyhow::Result;
use dashmap::DashMap;
use ethers_providers::StreamExt;
@ -13,6 +10,8 @@ use tokio_tungstenite::tungstenite::{client::IntoClientRequest, Message as Tungs
use tokio_tungstenite::{connect_async, tungstenite};
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
use lib::types::{core::*, http_client::*, http_server::*};
// Test http_client with these commands in the terminal
// !message our http_client {"method": "GET", "url": "https://jsonplaceholder.typicode.com/posts", "headers": {}}
// !message our http_client {"method": "POST", "url": "https://jsonplaceholder.typicode.com/posts", "headers": {"Content-Type": "application/json"}}

7
kinode/src/http/mod.rs Normal file
View File

@ -0,0 +1,7 @@
#![allow(unused)]
pub mod client;
pub mod server;
pub mod utils;
pub use lib::types::http_client as client_types;
pub use lib::types::http_server as server_types;

View File

@ -1,6 +1,5 @@
use crate::http::server_types::*;
use crate::http::utils::*;
use crate::types::*;
use crate::{keygen, register};
use anyhow::Result;
use dashmap::DashMap;
@ -16,6 +15,8 @@ use warp::http::{header::HeaderValue, StatusCode};
use warp::ws::{WebSocket, Ws};
use warp::{Filter, Reply};
use lib::types::core::*;
#[cfg(not(feature = "simulation-mode"))]
const HTTP_SELF_IMPOSED_TIMEOUT: u64 = 15;
#[cfg(feature = "simulation-mode")]
@ -51,6 +52,108 @@ struct BoundWsPath {
pub secure_subdomain: Option<String>,
pub authenticated: bool,
pub encrypted: bool, // TODO use
pub extension: bool,
}
async fn send_push(
id: u64,
lazy_load_blob: Option<LazyLoadBlob>,
source: Address,
send_to_loop: &MessageSender,
ws_senders: WebSocketSenders,
channel_id: u32,
message_type: WsMessageType,
maybe_ext: Option<MessageType>,
) -> bool {
let Some(mut blob) = lazy_load_blob else {
send_action_response(id, source, send_to_loop, Err(HttpServerError::NoBlob)).await;
return true;
};
if maybe_ext.is_some() {
let WsMessageType::Binary = message_type else {
// TODO
send_action_response(id, source, send_to_loop, Err(HttpServerError::NoBlob)).await;
return true;
};
let action = HttpServerAction::WebSocketExtPushData {
id,
kinode_message_type: maybe_ext.unwrap(),
blob: blob.bytes,
};
blob.bytes = rmp_serde::to_vec_named(&action).unwrap();
}
let ws_message = match message_type {
WsMessageType::Text => {
warp::ws::Message::text(String::from_utf8_lossy(&blob.bytes).to_string())
}
WsMessageType::Binary => warp::ws::Message::binary(blob.bytes),
WsMessageType::Ping | WsMessageType::Pong => {
if blob.bytes.len() > 125 {
send_action_response(
id,
source,
send_to_loop,
Err(HttpServerError::WebSocketPushError {
error: "Ping and Pong messages must be 125 bytes or less".to_string(),
}),
)
.await;
return true;
}
if message_type == WsMessageType::Ping {
warp::ws::Message::ping(blob.bytes)
} else {
warp::ws::Message::pong(blob.bytes)
}
}
WsMessageType::Close => {
unreachable!();
}
};
// Send to the websocket if registered
if let Some(got) = ws_senders.get(&channel_id) {
let owner_process = &got.value().0;
let sender = &got.value().1;
if owner_process != &source.process {
send_action_response(
id,
source,
send_to_loop,
Err(HttpServerError::WebSocketPushError {
error: "WebSocket channel not owned by this process".to_string(),
}),
)
.await;
return true;
}
match sender.send(ws_message).await {
Ok(_) => {}
Err(_) => {
send_action_response(
id,
source.clone(),
send_to_loop,
Err(HttpServerError::WebSocketPushError {
error: "WebSocket channel closed".to_string(),
}),
)
.await;
return true;
}
}
} else {
send_action_response(
id,
source.clone(),
send_to_loop,
Err(HttpServerError::WebSocketPushError {
error: "WebSocket channel not found".to_string(),
}),
)
.await;
return true;
}
false
}
/// HTTP server: a runtime module that handles HTTP requests at a given port.
@ -154,6 +257,7 @@ async fn serve(
let cloned_jwt_secret_bytes = jwt_secret_bytes.clone();
let cloned_print_tx = print_tx.clone();
let ws_route = warp::ws()
.and(warp::addr::remote())
.and(warp::path::full())
.and(warp::filters::host::optional())
.and(warp::filters::header::headers_cloned())
@ -263,6 +367,7 @@ async fn login_handler(
async fn ws_handler(
ws_connection: Ws,
socket_addr: Option<SocketAddr>,
path: warp::path::FullPath,
host: Option<Authority>,
headers: warp::http::HeaderMap,
@ -319,7 +424,16 @@ async fn ws_handler(
}
}
let is_local = socket_addr
.map(|addr| addr.ip().is_loopback())
.unwrap_or(false);
if bound_path.extension && !is_local {
return Err(warp::reject::reject());
}
let app = bound_path.app.clone();
let extension = bound_path.extension;
drop(ws_path_bindings);
@ -339,6 +453,7 @@ async fn ws_handler(
ws_senders.clone(),
send_to_loop.clone(),
print_tx.clone(),
extension,
)
.await;
}))
@ -646,6 +761,130 @@ async fn handle_rpc_message(
))
}
fn make_websocket_message(
our: String,
app: ProcessId,
channel_id: u32,
ws_msg_type: WsMessageType,
msg: Vec<u8>,
) -> Option<KernelMessage> {
Some(KernelMessage {
id: rand::random(),
source: Address {
node: our.to_string(),
process: HTTP_SERVER_PROCESS_ID.clone(),
},
target: Address {
node: our.to_string(),
process: app,
},
rsvp: None,
message: Message::Request(Request {
inherit: false,
expects_response: None,
body: serde_json::to_vec(&HttpServerRequest::WebSocketPush {
channel_id,
message_type: ws_msg_type,
})
.unwrap(),
metadata: None,
capabilities: vec![],
}),
lazy_load_blob: Some(LazyLoadBlob {
mime: None,
bytes: msg,
}),
})
}
fn make_ext_websocket_message(
our: String,
app: ProcessId,
channel_id: u32,
ws_msg_type: WsMessageType,
msg: Vec<u8>,
) -> Option<KernelMessage> {
let option = match rmp_serde::from_slice::<HttpServerAction>(&msg) {
Err(_) => Some((
rand::random(),
Message::Request(Request {
inherit: false,
expects_response: None,
body: serde_json::to_vec(&HttpServerRequest::WebSocketPush {
channel_id,
message_type: ws_msg_type,
})
.unwrap(),
metadata: None,
capabilities: vec![],
}),
Some(LazyLoadBlob {
mime: None,
bytes: msg,
}),
)),
Ok(HttpServerAction::WebSocketExtPushData {
id,
kinode_message_type,
blob,
}) => Some((
id,
match kinode_message_type {
MessageType::Request => Message::Request(Request {
inherit: false,
expects_response: None,
body: serde_json::to_vec(&HttpServerRequest::WebSocketPush {
channel_id,
message_type: ws_msg_type,
})
.unwrap(),
metadata: None,
capabilities: vec![],
}),
MessageType::Response => Message::Response((
Response {
inherit: false,
body: serde_json::to_vec(&HttpServerRequest::WebSocketPush {
channel_id,
message_type: ws_msg_type,
})
.unwrap(),
metadata: None,
capabilities: vec![],
},
None,
)),
},
Some(LazyLoadBlob {
mime: None,
bytes: blob,
}),
)),
Ok(m) => {
println!("http server: got unexpected message from ext websocket: {m:?}\r");
None
}
};
let Some((id, message, blob)) = option else {
return None;
};
Some(KernelMessage {
id,
source: Address {
node: our.to_string(),
process: HTTP_SERVER_PROCESS_ID.clone(),
},
target: Address {
node: our.to_string(),
process: app,
},
rsvp: None,
message,
lazy_load_blob: blob,
})
}
async fn maintain_websocket(
ws: WebSocket,
our: Arc<String>,
@ -655,6 +894,7 @@ async fn maintain_websocket(
ws_senders: WebSocketSenders,
send_to_loop: MessageSender,
print_tx: PrintSender,
extension: bool,
) {
let (mut write_stream, mut read_stream) = ws.split();
@ -693,6 +933,12 @@ async fn maintain_websocket(
})
.await;
let make_ws_message = if extension {
make_ext_websocket_message
} else {
make_websocket_message
};
loop {
tokio::select! {
read = read_stream.next() => {
@ -711,32 +957,15 @@ async fn maintain_websocket(
WsMessageType::Close
};
let _ = send_to_loop.send(KernelMessage {
id: rand::random(),
source: Address {
node: our.to_string(),
process: HTTP_SERVER_PROCESS_ID.clone(),
},
target: Address {
node: our.to_string(),
process: app.clone(),
},
rsvp: None,
message: Message::Request(Request {
inherit: false,
expects_response: None,
body: serde_json::to_vec(&HttpServerRequest::WebSocketPush {
channel_id,
message_type: ws_msg_type,
}).unwrap(),
metadata: None,
capabilities: vec![],
}),
lazy_load_blob: Some(LazyLoadBlob {
mime: None,
bytes: msg.into_bytes(),
}),
}).await;
if let Some(message) = make_ws_message(
our.to_string(),
app.clone(),
channel_id,
ws_msg_type,
msg.into_bytes(),
) {
let _ = send_to_loop.send(message).await;
}
}
_ => {
websocket_close(channel_id, app.clone(), &ws_senders, &send_to_loop).await;
@ -988,6 +1217,7 @@ async fn handle_app_message(
mut path,
authenticated,
encrypted,
extension,
} => {
path = if path.starts_with('/') {
format!("/{}{}", km.source.process, path)
@ -1002,12 +1232,14 @@ async fn handle_app_message(
secure_subdomain: None,
authenticated,
encrypted,
extension,
},
);
}
HttpServerAction::WebSocketSecureBind {
mut path,
encrypted,
extension,
} => {
path = if path.starts_with('/') {
format!("/{}{}", km.source.process, path)
@ -1025,6 +1257,7 @@ async fn handle_app_message(
secure_subdomain: Some(subdomain),
authenticated: true,
encrypted,
extension,
},
);
}
@ -1044,88 +1277,52 @@ async fn handle_app_message(
channel_id,
message_type,
} => {
let Some(blob) = km.lazy_load_blob else {
send_action_response(
km.id,
km.source,
&send_to_loop,
Err(HttpServerError::NoBlob),
)
.await;
let is_return = send_push(
km.id,
km.lazy_load_blob,
km.source.clone(),
&send_to_loop,
ws_senders,
channel_id,
message_type,
None,
)
.await;
if is_return {
return;
};
let ws_message = match message_type {
WsMessageType::Text => warp::ws::Message::text(
String::from_utf8_lossy(&blob.bytes).to_string(),
),
WsMessageType::Binary => warp::ws::Message::binary(blob.bytes),
WsMessageType::Ping | WsMessageType::Pong => {
if blob.bytes.len() > 125 {
send_action_response(
km.id,
km.source,
&send_to_loop,
Err(HttpServerError::WebSocketPushError {
error: "Ping and Pong messages must be 125 bytes or less"
.to_string(),
}),
)
.await;
return;
}
if message_type == WsMessageType::Ping {
warp::ws::Message::ping(blob.bytes)
} else {
warp::ws::Message::pong(blob.bytes)
}
}
WsMessageType::Close => {
unreachable!();
}
};
// Send to the websocket if registered
if let Some(got) = ws_senders.get(&channel_id) {
let owner_process = &got.value().0;
let sender = &got.value().1;
if owner_process != &km.source.process {
send_action_response(
km.id,
km.source,
&send_to_loop,
Err(HttpServerError::WebSocketPushError {
error: "WebSocket channel not owned by this process"
.to_string(),
}),
)
.await;
return;
}
match sender.send(ws_message).await {
Ok(_) => {}
Err(_) => {
send_action_response(
km.id,
km.source.clone(),
&send_to_loop,
Err(HttpServerError::WebSocketPushError {
error: "WebSocket channel closed".to_string(),
}),
)
.await;
}
}
} else {
send_action_response(
km.id,
km.source.clone(),
&send_to_loop,
Err(HttpServerError::WebSocketPushError {
error: "WebSocket channel not found".to_string(),
}),
)
.await;
}
}
HttpServerAction::WebSocketExtPushOutgoing {
channel_id,
message_type,
desired_reply_type,
} => {
send_push(
km.id,
km.lazy_load_blob,
km.source.clone(),
&send_to_loop,
ws_senders,
channel_id,
message_type,
Some(desired_reply_type),
)
.await;
return;
}
HttpServerAction::WebSocketExtPushData { .. } => {
send_action_response(
km.id,
km.source,
&send_to_loop,
Err(HttpServerError::WebSocketPushError {
error: "Use WebSocketExtPushOutgoing, not WebSocketExtPushData"
.to_string(),
}),
)
.await;
return;
}
HttpServerAction::WebSocketClose(channel_id) => {
if let Some(got) = ws_senders.get(&channel_id) {
if got.value().0 != km.source.process {

View File

@ -1,4 +1,3 @@
use crate::http::server_types::*;
use hmac::{Hmac, Mac};
use jwt::VerifyWithKey;
use serde::{Deserialize, Serialize};
@ -7,6 +6,8 @@ use std::collections::HashMap;
use tokio::net::TcpListener;
use warp::http::{header::HeaderName, header::HeaderValue, HeaderMap};
use lib::types::http_server::*;
#[derive(Serialize, Deserialize)]
pub struct RpcMessage {
pub node: Option<String>,

View File

@ -1,5 +1,3 @@
use crate::types::STATE_PROCESS_ID;
use crate::types::{self as t, VFS_PROCESS_ID};
use crate::KERNEL_PROCESS_ID;
use anyhow::Result;
use ring::signature;
@ -10,6 +8,8 @@ use tokio::sync::mpsc;
use tokio::task::JoinHandle;
use wasmtime::{Config, Engine, WasmBacktraceDetails};
use lib::types::core::{self as t, STATE_PROCESS_ID, VFS_PROCESS_ID};
/// Manipulate a single process.
pub mod process;
/// Implement the functions served to processes by `kinode.wit`.
@ -431,12 +431,18 @@ async fn handle_kernel_request(
let _ = send_to_terminal
.send(t::Printout {
verbosity: 2,
content: format!("kernel: no such process {:?} to kill", process_id),
content: format!("kernel: no such process {process_id} to kill"),
})
.await;
return;
}
};
let _ = send_to_terminal
.send(t::Printout {
verbosity: 2,
content: format!("killing process {process_id}"),
})
.await;
process_handle.abort();
process_map.remove(&process_id);
let _ = persist_state(&our_name, &send_to_loop, process_map).await;

View File

@ -1,9 +1,8 @@
use crate::kernel::{ProcessMessageReceiver, ProcessMessageSender};
use crate::types as t;
use crate::KERNEL_PROCESS_ID;
use anyhow::Result;
pub use kinode::process::standard as wit;
pub use kinode::process::standard::Host as StandardHost;
//pub use kinode::process::standard as wit;
//pub use kinode::process::standard::Host as StandardHost;
use ring::signature::{self, KeyPair};
use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
@ -12,11 +11,16 @@ use wasmtime::component::*;
use wasmtime::{Engine, Store};
use wasmtime_wasi::preview2::{pipe::MemoryOutputPipe, Table, WasiCtx, WasiCtxBuilder, WasiView};
bindgen!({
path: "wit",
world: "process",
async: true,
});
use lib::types::core as t;
pub use lib::wit;
pub use lib::wit::Host as StandardHost;
pub use lib::Process;
// bindgen!({
// path: "wit",
// world: "process",
// async: true,
// });
pub struct ProcessState {
pub keypair: Arc<signature::Ed25519KeyPair>,

View File

@ -1,12 +1,14 @@
use crate::kernel::process;
use crate::kernel::process::kinode::process::standard as wit;
use crate::kernel::process::StandardHost;
use crate::types as t;
use crate::types::STATE_PROCESS_ID;
//use crate::kernel::process::kinode::process::standard as wit;
//use crate::kernel::process::StandardHost;
use crate::KERNEL_PROCESS_ID;
use crate::VFS_PROCESS_ID;
use anyhow::Result;
use lib::types::core::{self as t, STATE_PROCESS_ID};
pub use lib::wit;
pub use lib::wit::Host as StandardHost;
async fn print_debug(proc: &process::ProcessState, content: &str) {
let _ = proc
.send_to_terminal

View File

@ -11,7 +11,7 @@ use ring::signature::{self, KeyPair};
use ring::{digest as ring_digest, rand::SecureRandom};
use std::num::NonZeroU32;
use crate::types::Keyfile;
use lib::types::core::Keyfile;
type DiskKey = [u8; CREDENTIAL_LEN];

View File

@ -7,7 +7,7 @@ use std::sync::Arc;
use tokio::fs;
use tokio::sync::Mutex;
use crate::types::*;
use lib::types::core::*;
pub async fn kv(
our_node: String,
@ -192,7 +192,7 @@ async fn handle_request(
match tx_id {
None => {
db.put(key, blob.bytes)?;
db.put(key, blob.bytes).map_err(rocks_to_kv_err)?;
}
Some(tx_id) => {
let mut tx = match txs.get_mut(tx_id) {
@ -216,7 +216,7 @@ async fn handle_request(
};
match tx_id {
None => {
db.delete(key)?;
db.delete(key).map_err(rocks_to_kv_err)?;
}
Some(tx_id) => {
let mut tx = match txs.get_mut(tx_id) {
@ -250,11 +250,11 @@ async fn handle_request(
match action {
KvAction::Set { key, .. } => {
if let Some(blob) = blob {
tx.put(&key, &blob)?;
tx.put(&key, &blob).map_err(rocks_to_kv_err)?;
}
}
KvAction::Delete { key, .. } => {
tx.delete(&key)?;
tx.delete(&key).map_err(rocks_to_kv_err)?;
}
_ => {}
}
@ -274,7 +274,7 @@ async fn handle_request(
// looping through open dbs and flushing their memtables
for db_ref in open_kvs.iter() {
let db = db_ref.value();
db.flush()?;
db.flush().map_err(rocks_to_kv_err)?;
}
(serde_json::to_vec(&KvResponse::Ok).unwrap(), None)
}
@ -424,7 +424,7 @@ async fn check_caps(
let db_path = format!("{}/{}/{}", kv_path, request.package_id, request.db);
fs::create_dir_all(&db_path).await?;
let db = OptimisticTransactionDB::open_default(&db_path)?;
let db = OptimisticTransactionDB::open_default(&db_path).map_err(rocks_to_kv_err)?;
open_kvs.insert((request.package_id.clone(), request.db.clone()), db);
Ok(())
@ -497,40 +497,9 @@ fn make_error_message(our_name: String, km: &KernelMessage, error: KvError) -> K
}
}
impl std::fmt::Display for KvAction {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl From<tokio::sync::oneshot::error::RecvError> for KvError {
fn from(err: tokio::sync::oneshot::error::RecvError) -> Self {
KvError::NoCap {
error: err.to_string(),
}
}
}
impl From<tokio::sync::mpsc::error::SendError<CapMessage>> for KvError {
fn from(err: tokio::sync::mpsc::error::SendError<CapMessage>) -> Self {
KvError::NoCap {
error: err.to_string(),
}
}
}
impl From<std::io::Error> for KvError {
fn from(err: std::io::Error) -> Self {
KvError::IOError {
error: err.to_string(),
}
}
}
impl From<rocksdb::Error> for KvError {
fn from(error: rocksdb::Error) -> Self {
KvError::RocksDBError {
action: "".into(),
error: error.to_string(),
}
fn rocks_to_kv_err(error: rocksdb::Error) -> KvError {
KvError::RocksDBError {
action: "".into(),
error: error.to_string(),
}
}

View File

@ -1,6 +1,5 @@
#![feature(btree_extract_if)]
use crate::types::*;
use anyhow::Result;
use clap::{arg, value_parser, Command};
use std::env;
@ -8,6 +7,8 @@ use std::sync::Arc;
use tokio::sync::{mpsc, oneshot};
use tokio::{fs, time::timeout};
use lib::types::core::*;
#[cfg(feature = "simulation-mode")]
use ring::{rand::SystemRandom, signature, signature::KeyPair};
@ -22,7 +23,6 @@ mod sqlite;
mod state;
mod terminal;
mod timer;
mod types;
mod vfs;
const EVENT_LOOP_CHANNEL_CAPACITY: usize = 10_000;
@ -140,6 +140,32 @@ async fn main() {
register::KNS_OPTIMISM_ADDRESS
};
// check .testnet file for true/false in order to enforce testnet mode on subsequent boots of this node
match fs::read(format!("{}/.testnet", home_directory_path)).await {
Ok(contents) => {
if contents == b"true" {
if !on_testnet {
println!("\x1b[38;5;196mfatal: this is a testnet node, and must be booted with the --testnet flag. exiting.\x1b[0m");
return;
}
} else if contents == b"false" {
if on_testnet {
println!("\x1b[38;5;196mfatal: this is a mainnet node, and must be booted without the --testnet flag. exiting.\x1b[0m");
return;
}
} else {
panic!("invalid contents of .testnet file");
}
}
Err(_) => {
let _ = fs::write(
format!("{}/.testnet", home_directory_path),
format!("{}", on_testnet),
)
.await;
}
}
#[cfg(not(feature = "simulation-mode"))]
let (rpc_url, is_detached) = (matches.get_one::<String>("rpc").unwrap(), false);

View File

@ -5,7 +5,7 @@ use tokio_tungstenite::{
tungstenite::protocol::Message::{Binary, Text},
};
use crate::types;
use lib::types::core as types;
type Sender = mpsc::Sender<types::KernelMessage>;
type Receiver = mpsc::Receiver<types::KernelMessage>;

View File

@ -21,7 +21,7 @@ mod utils;
#[cfg(not(feature = "simulation-mode"))]
use crate::net::{types::*, utils::*};
#[cfg(not(feature = "simulation-mode"))]
use crate::types::*;
use lib::types::core::*;
// Re-export for testing.
#[cfg(feature = "simulation-mode")]

View File

@ -1,4 +1,3 @@
use crate::types::*;
use dashmap::DashMap;
use futures::stream::{SplitSink, SplitStream};
use serde::{Deserialize, Serialize};
@ -8,6 +7,8 @@ use tokio::net::TcpStream;
use tokio::sync::mpsc::UnboundedSender;
use tokio_tungstenite::{tungstenite, MaybeTlsStream, WebSocketStream};
use lib::types::core::*;
/// Sent to a node when you want to connect directly to them.
/// Sent in the 'e, ee, s, es' and 's, se' phases of XX noise protocol pattern.
#[derive(Debug, Deserialize, Serialize)]

View File

@ -1,5 +1,4 @@
use crate::net::{types::*, MESSAGE_MAX_SIZE, TIMEOUT};
use crate::types::*;
use anyhow::{anyhow, Result};
use futures::stream::{SplitSink, SplitStream};
use futures::{SinkExt, StreamExt};
@ -10,6 +9,8 @@ use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
use tokio::time::timeout;
use tokio_tungstenite::{connect_async, tungstenite, MaybeTlsStream, WebSocketStream};
use lib::types::core::*;
lazy_static::lazy_static! {
static ref PARAMS: NoiseParams = "Noise_XX_25519_ChaChaPoly_BLAKE2s"
.parse()

Some files were not shown because too many files have changed in this diff Show More