Merge branch 'develop' into bp/new-eth

This commit is contained in:
bitful-pannul 2024-02-15 19:12:31 -03:00
commit f0892203f2
22 changed files with 371 additions and 1499 deletions

1383
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -17,13 +17,14 @@ async fn main() -> anyhow::Result<()> {
let mut bootstrapped_processes = Vec::new();
writeln!(
bootstrapped_processes,
"pub static BOOTSTRAPPED_PROCESSES: &[(&str, &[u8])] = &[",
"pub static BOOTSTRAPPED_PROCESSES: &[(&str, &[u8], &[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 metadata_path = format!("{}/metadata.json", entry_path.display());
let parent_pkg_path = format!("{}/pkg", entry_path.display());
kit::build::execute(&entry_path, false, false, false, true).await?;
@ -72,8 +73,8 @@ async fn main() -> anyhow::Result<()> {
// Add zip bytes to bootstrapped_processes.rs
writeln!(
bootstrapped_processes,
" (\"{}\", include_bytes!(\"{}\")),",
zip_filename, zip_path,
" (\"{}\", include_bytes!(\"{}\"), include_bytes!(\"{}\")),",
zip_filename, metadata_path, zip_path,
)
.unwrap();
}

View File

@ -30,7 +30,7 @@ pub fn handle_http_request(
req: &IncomingHttpRequest,
) -> anyhow::Result<()> {
match serve_paths(our, state, requested_packages, req) {
Ok((status_code, headers, body)) => send_response(
Ok((status_code, _headers, body)) => send_response(
status_code,
Some(HashMap::from([(
String::from("Content-Type"),
@ -81,6 +81,7 @@ fn gen_package_info(
"caps_approved": state.caps_approved,
"mirroring": state.mirroring,
"auto_update": state.auto_update,
"verified": state.verified,
}),
None => json!(null),
},
@ -188,7 +189,7 @@ fn serve_paths(
}
Method::PUT => {
// update an app
let pkg_listing: &PackageListing = state
let _pkg_listing: &PackageListing = state
.get_listing(&package_id)
.ok_or(anyhow::anyhow!("No package"))?;
let pkg_state: &PackageState = state
@ -277,10 +278,10 @@ fn serve_paths(
let mirrors: &Vec<NodeId> = pkg_listing
.metadata
.as_ref()
.ok_or(anyhow::anyhow!("No metadata for package {package_id}"))?
.expect("Package does not have metadata")
.properties
.mirrors
.as_ref()
.ok_or(anyhow::anyhow!("No mirrors for package {package_id}"))?;
.as_ref();
let download_from = body_json
.get("download_from")
.unwrap_or(&json!(mirrors

View File

@ -283,6 +283,7 @@ fn handle_local_request(
mirrored_from: Some(our.node.clone()),
our_version,
installed: false,
verified: true, // side loaded apps are implicitly verified because there is no "source" to verify against
caps_approved: true, // TODO see if we want to auto-approve local installs
manifest_hash: None, // generated in the add fn
mirroring: *mirror,
@ -425,35 +426,56 @@ fn handle_receive_download(
// 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);
let mut verified = false;
match requested_package.desired_version_hash {
Some(hash) => {
if download_hash != hash {
return Err(anyhow::anyhow!(
"app store: downloaded package is not desired version--rejecting download! download hash: {download_hash}, desired hash: {hash}"
));
if hash.is_empty() {
println!(
"\x1b[33mwarning: downloaded package has no version hashes--cannot verify code integrity, proceeding anyways\x1b[0m"
);
} else {
return Err(anyhow::anyhow!(
"app store: downloaded package is not desired version--rejecting download! download hash: {download_hash}, desired hash: {hash}"
));
}
} else {
verified = true;
}
}
None => {
// check against latest from listing
// check against `metadata.properties.current_version`
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! download hash: {download_hash}, latest hash: {latest_hash}"
));
}
let Some(metadata) = &package_listing.metadata else {
return Err(anyhow::anyhow!(
"app store: downloaded package has no metadata to check validity against!"
));
};
let Some(latest_hash) = metadata
.properties
.code_hashes
.get(&metadata.properties.current_version)
else {
return Err(anyhow::anyhow!(
"app store: downloaded package has no versions in manager--rejecting download!"
));
};
if &download_hash != latest_hash {
if latest_hash.is_empty() {
println!(
"\x1b[33mwarning: downloaded package has no version hashes--cannot verify code integrity, proceeding anyways\x1b[0m"
);
} else {
return Err(anyhow::anyhow!(
"app store: downloaded package has no versions in manager--rejecting download!"
"app store: downloaded package is not latest version--rejecting download! download hash: {download_hash}, latest hash: {latest_hash}"
));
}
} else {
println!("app store: warning: downloaded package has no listing metadata to check validity against!")
verified = true;
}
}
}
@ -472,6 +494,7 @@ fn handle_receive_download(
mirrored_from: Some(requested_package.from),
our_version: download_hash,
installed: false,
verified,
caps_approved: false,
manifest_hash: None, // generated in the add fn
mirroring: requested_package.mirror,

View File

@ -50,22 +50,7 @@ pub struct PackageListing {
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>>,
pub metadata: Option<kt::Erc721Metadata>,
}
#[derive(Debug, Serialize, Deserialize)]
@ -86,13 +71,14 @@ pub struct PackageState {
/// the version of the package we have downloaded
pub our_version: String,
pub installed: bool,
pub verified: bool,
pub caps_approved: bool,
pub manifest_hash: Option<String>,
/// are we serving this package to others?
pub mirroring: bool,
/// if we get a listing data update, will we try to download it?
pub auto_update: bool,
pub metadata: Option<OnchainPackageMetadata>,
pub metadata: Option<kt::Erc721Metadata>,
}
/// this process's saved state
@ -318,6 +304,7 @@ impl State {
mirrored_from: None,
our_version,
installed: true,
verified: true, // implicity verified
caps_approved: true, // since it's already installed this must be true
manifest_hash: Some(generate_metadata_hash(&manifest_bytes)),
mirroring: false,
@ -421,6 +408,15 @@ impl State {
let metadata = fetch_metadata(&metadata_url, &metadata_hash).ok();
if let Some(metadata) = &metadata {
if metadata.properties.publisher != publisher_name {
return Err(anyhow::anyhow!(format!(
"app store: metadata publisher name mismatch: got {}, expected {}",
metadata.properties.publisher, publisher_name
)));
}
}
let listing = match self.get_listing_with_hash_mut(&package_hash) {
Some(current_listing) => {
current_listing.name = package_name;
@ -460,7 +456,15 @@ impl State {
))?;
let metadata = match fetch_metadata(&metadata_url, &metadata_hash) {
Ok(metadata) => Some(metadata),
Ok(metadata) => {
if metadata.properties.publisher != current_listing.publisher {
return Err(anyhow::anyhow!(format!(
"app store: metadata publisher name mismatch: got {}, expected {}",
metadata.properties.publisher, current_listing.publisher
)));
}
Some(metadata)
}
Err(e) => {
crate::print_to_terminal(
1,
@ -583,10 +587,7 @@ fn dnswire_decode(wire_format_bytes: &[u8]) -> Result<String, std::string::FromU
}
/// fetch metadata from metadata_url and verify it matches metadata_hash
fn fetch_metadata(
metadata_url: &str,
metadata_hash: &str,
) -> anyhow::Result<OnchainPackageMetadata> {
fn fetch_metadata(metadata_url: &str, metadata_hash: &str) -> anyhow::Result<kt::Erc721Metadata> {
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 {
@ -594,9 +595,7 @@ fn fetch_metadata(
};
let hash = generate_metadata_hash(&body.bytes);
if &hash == metadata_hash {
Ok(serde_json::from_slice::<OnchainPackageMetadata>(
&body.bytes,
)?)
Ok(serde_json::from_slice::<kt::Erc721Metadata>(&body.bytes)?)
} else {
Err(anyhow::anyhow!(
"metadata hash mismatch: got {hash}, expected {metadata_hash}"

View File

@ -0,0 +1,16 @@
{
"name": "App Store",
"description": "A package manager + app store.",
"image": "",
"properties": {
"package_name": "app_store",
"current_version": "0.3.0",
"publisher": "sys",
"mirrors": [],
"code_hashes": {
"0.3.0": ""
}
},
"external_url": "https://kinode.org",
"animation_url": ""
}

View File

@ -1,10 +0,0 @@
{
"package": "app_store",
"publisher": "sys",
"version": [
0,
2,
0
],
"description": "A package manager + app store."
}

File diff suppressed because one or more lines are too long

View File

@ -18,7 +18,7 @@
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'>
<script type="module" crossorigin src="/main:app_store:sys/assets/index-A09g5OKk.js"></script>
<script type="module" crossorigin src="/main:app_store:sys/assets/index-Wv-dWa0C.js"></script>
<link rel="stylesheet" crossorigin href="/main:app_store:sys/assets/index-aUqPNadJ.css">
</head>
<body>

View File

@ -0,0 +1,16 @@
{
"name": "Chess by Kinode",
"description": "A peer-to-peer chess game",
"image": "",
"properties": {
"package_name": "chess",
"current_version": "0.2.0",
"publisher": "sys",
"mirrors": [],
"code_hashes": {
"0.2.0": ""
}
},
"external_url": "https://kinode.org",
"animation_url": ""
}

View File

@ -1,9 +0,0 @@
{
"package": "chess",
"publisher": "sys",
"version": [
0,
2,
0
]
}

View File

@ -0,0 +1,16 @@
{
"name": "Homepage",
"description": "Homepage for Kinode OS",
"image": "",
"properties": {
"package_name": "homepage",
"current_version": "0.1.0",
"publisher": "sys",
"mirrors": [],
"code_hashes": {
"0.1.0": ""
}
},
"external_url": "https://kinode.org",
"animation_url": ""
}

View File

@ -1,9 +0,0 @@
{
"package": "homepage",
"publisher": "sys",
"version": [
0,
1,
0
]
}

View File

@ -0,0 +1,16 @@
{
"name": "KNS Indexer",
"description": "Kinode OS pki indexer",
"image": "",
"properties": {
"package_name": "kns_indexer",
"current_version": "0.1.0",
"publisher": "sys",
"mirrors": [],
"code_hashes": {
"0.1.0": ""
}
},
"external_url": "https://kinode.org",
"animation_url": ""
}

View File

@ -1,9 +0,0 @@
{
"package": "kns_indexer",
"publisher": "sys",
"version": [
0,
1,
0
]
}

View File

@ -0,0 +1,16 @@
{
"name": "Terminal",
"description": "Default script runner for Kinode OS",
"image": "",
"properties": {
"package_name": "terminal",
"current_version": "0.1.0",
"publisher": "sys",
"mirrors": [],
"code_hashes": {
"0.1.0": ""
}
},
"external_url": "https://kinode.org",
"animation_url": ""
}

View File

@ -1,9 +0,0 @@
{
"package": "terminal",
"publisher": "sys",
"version": [
0,
1,
0
]
}

View File

@ -0,0 +1,16 @@
{
"name": "Tester",
"description": "Package for testing other packages",
"image": "",
"properties": {
"package_name": "tester",
"current_version": "0.1.0",
"publisher": "sys",
"mirrors": [],
"code_hashes": {
"0.1.0": ""
}
},
"external_url": "https://kinode.org",
"animation_url": ""
}

View File

@ -1,9 +0,0 @@
{
"package": "tester",
"publisher": "sys",
"version": [
0,
1,
0
]
}

View File

@ -389,7 +389,8 @@ async fn bootstrap(
let packages = get_zipped_packages().await;
for (package_name, mut package) in packages.clone() {
for (package_metadata, mut package) in packages.clone() {
let package_name = package_metadata.properties.package_name.as_str();
// special case tester: only load it in if in simulation mode
if package_name == "tester" {
#[cfg(not(feature = "simulation-mode"))]
@ -397,31 +398,7 @@ async fn bootstrap(
}
println!("fs: handling package {package_name}...\r");
// get and read metadata.json
let Ok(mut package_metadata_zip) = package.by_name("metadata.json") else {
println!(
"fs: missing metadata for package {}, skipping",
package_name
);
continue;
};
let mut metadata_content = Vec::new();
package_metadata_zip
.read_to_end(&mut metadata_content)
.unwrap();
drop(package_metadata_zip);
let package_metadata: serde_json::Value =
serde_json::from_slice(&metadata_content).expect("fs: metadata parse error");
// println!("fs: found package metadata: {:?}\r", package_metadata);
let package_name = package_metadata["package"]
.as_str()
.expect("fs: metadata parse error: bad package name");
let package_publisher = package_metadata["publisher"]
.as_str()
.expect("fs: metadata parse error: bad publisher name");
let package_publisher = package_metadata.properties.publisher.as_str();
// create a new package in VFS
let our_drive_name = [package_name, package_publisher].join(":");
@ -630,7 +607,8 @@ async fn bootstrap(
}
// second loop: go and grant_capabilities to processes
// can't do this in first loop because we need to have all processes in the map first
for (package_name, mut package) in packages {
for (package_metadata, mut package) in packages {
let package_name = package_metadata.properties.package_name.as_str();
// special case tester: only load it in if in simulation mode
if package_name == "tester" {
#[cfg(not(feature = "simulation-mode"))]
@ -654,31 +632,7 @@ async fn bootstrap(
let package_manifest = serde_json::from_str::<Vec<PackageManifestEntry>>(&package_manifest)
.expect("fs: manifest parse error");
// get and read metadata.json
let Ok(mut package_metadata_zip) = package.by_name("metadata.json") else {
println!(
"fs: missing metadata for package {}, skipping",
package_name
);
continue;
};
let mut metadata_content = Vec::new();
package_metadata_zip
.read_to_end(&mut metadata_content)
.unwrap();
drop(package_metadata_zip);
let package_metadata: serde_json::Value =
serde_json::from_slice(&metadata_content).expect("fs: metadata parse error");
// println!("fs: found package metadata: {:?}\r", package_metadata);
let package_name = package_metadata["package"]
.as_str()
.expect("fs: metadata parse error: bad package name");
let package_publisher = package_metadata["publisher"]
.as_str()
.expect("fs: metadata parse error: bad publisher name");
let package_publisher = package_metadata.properties.publisher.as_str();
// for each process-entry in manifest.json:
for entry in package_manifest {
@ -759,16 +713,24 @@ fn sign_cap(cap: Capability, keypair: Arc<signature::Ed25519KeyPair>) -> Vec<u8>
}
/// read in `include!()`ed .zip package files
async fn get_zipped_packages() -> Vec<(String, zip::ZipArchive<std::io::Cursor<&'static [u8]>>)> {
async fn get_zipped_packages() -> Vec<(
Erc721Metadata,
zip::ZipArchive<std::io::Cursor<&'static [u8]>>,
)> {
// println!("fs: reading distro packages...\r");
let mut packages = Vec::new();
for (package_name, bytes) in BOOTSTRAPPED_PROCESSES.iter() {
for (package_name, metadata_bytes, bytes) in BOOTSTRAPPED_PROCESSES.iter() {
if let Ok(zip) = zip::ZipArchive::new(std::io::Cursor::new(*bytes)) {
// add to list of packages
// println!("fs: found package: {}\r", package_name);
packages.push((package_name.to_string(), zip));
if let Ok(metadata) = serde_json::from_slice::<Erc721Metadata>(metadata_bytes) {
packages.push((metadata, zip));
} else {
println!(
"fs: metadata for package {} is not valid Erc721Metadata",
package_name
);
}
}
}

View File

@ -1042,17 +1042,47 @@ impl std::fmt::Display for PersistedProcess {
}
}
pub type PackageVersion = (u32, u32, u32);
/// the type that gets deserialized from `metadata.json` in a package
#[derive(Debug, Serialize, Deserialize)]
pub struct PackageMetadata {
pub package: String,
pub publisher: String,
pub version: PackageVersion,
pub wit_version: Option<(u32, u32, u32)>,
/// Represents the metadata associated with a kinode package, which is an ERC721 compatible token.
/// This is deserialized from the `metadata.json` file in a package.
/// Fields:
/// - `name`: An optional field representing the display name of the package. This does not have to be unique, and is not used for identification purposes.
/// - `description`: An optional field providing a description of the package.
/// - `image`: An optional field containing a URL to an image representing the package.
/// - `external_url`: An optional field containing a URL for more information about the package. For example, a link to the github repository.
/// - `animation_url`: An optional field containing a URL to an animation or video representing the package.
/// - `properties`: A requried field containing important information about the package.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Erc721Metadata {
pub name: Option<String>,
pub description: Option<String>,
pub website: Option<String>,
pub image: Option<String>,
pub external_url: Option<String>,
pub animation_url: Option<String>,
pub properties: Erc721Properties,
}
/// Represents critical fields of a kinode package in an ERC721 compatible format.
/// This follows the [ERC1155](https://github.com/ethereum/ercs/blob/master/ERCS/erc-1155.md#erc-1155-metadata-uri-json-schema) metadata standard.
///
/// Fields:
/// - `package_name`: The unique name of the package, used in the `PackageId`, e.g. `package_name:publisher`.
/// - `publisher`: The KNS identity of the package publisher used in the `PackageId`, e.g. `package_name:publisher`
/// - `current_version`: A string representing the current version of the package, e.g. `1.0.0`.
/// - `mirrors`: A list of NodeIds where the package can be found, providing redundancy.
/// - `code_hashes`: A map from version names to their respective SHA-256 hashes.
/// - `license`: An optional field containing the license of the package.
/// - `screenshots`: An optional field containing a list of URLs to screenshots of the package.
/// - `wit_version`: An optional field containing the version of the WIT standard that the package adheres to.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Erc721Properties {
pub package_name: String,
pub publisher: String,
pub current_version: String,
pub mirrors: Vec<NodeId>,
pub code_hashes: HashMap<String, String>,
pub license: Option<String>,
pub screenshots: Option<Vec<String>>,
pub wit_version: Option<(u32, u32, u32)>,
}
/// the type that gets deserialized from each entry in the array in `manifest.json`

View File

@ -8,7 +8,7 @@ import zipfile
def get_system_info():
# Get OS and architecture information
os_info = subprocess.run(["uname"], capture_output=True, text=True, check=True).stdout.strip().lower()
arch_info = subprocess.run(["uname", "-p"], capture_output=True, text=True, check=True).stdout.strip().lower()
arch_info = subprocess.run(["uname", "-m"], capture_output=True, text=True, check=True).stdout.strip().lower()
if os_info == "linux":
os_info = "unknown-linux-gnu"