mirror of
https://github.com/uqbar-dao/nectar.git
synced 2024-11-22 19:34:06 +03:00
WIP
This commit is contained in:
parent
1620538e46
commit
f5257ae40d
BIN
modules/chess/package.zip
Normal file
BIN
modules/chess/package.zip
Normal file
Binary file not shown.
17
modules/chess/package/chess.html
Normal file
17
modules/chess/package/chess.html
Normal file
File diff suppressed because one or more lines are too long
BIN
modules/chess/package/chess.wasm
Normal file
BIN
modules/chess/package/chess.wasm
Normal file
Binary file not shown.
1
modules/chess/package/index.css
Normal file
1
modules/chess/package/index.css
Normal file
File diff suppressed because one or more lines are too long
113
modules/chess/package/index.js
Normal file
113
modules/chess/package/index.js
Normal file
File diff suppressed because one or more lines are too long
19
modules/chess/package/manifest.json
Normal file
19
modules/chess/package/manifest.json
Normal file
@ -0,0 +1,19 @@
|
||||
[
|
||||
{
|
||||
"process_id": "chess",
|
||||
"process_wasm": "chess.wasm",
|
||||
"on_panic": {
|
||||
"on_panic": "Restart"
|
||||
},
|
||||
"networking": true,
|
||||
"request_messaging": [
|
||||
"http_bindings",
|
||||
"encryptor"
|
||||
],
|
||||
"grant_messaging": [
|
||||
"terminal",
|
||||
"http_bindings",
|
||||
"encryptor"
|
||||
]
|
||||
}
|
||||
]
|
BIN
modules/terminal/package.zip
Normal file
BIN
modules/terminal/package.zip
Normal file
Binary file not shown.
14
modules/terminal/package/manifest.json
Normal file
14
modules/terminal/package/manifest.json
Normal file
@ -0,0 +1,14 @@
|
||||
[
|
||||
{
|
||||
"process_id": "terminal",
|
||||
"process_wasm": "terminal.wasm",
|
||||
"on_panic": {
|
||||
"on_panic": "Restart"
|
||||
},
|
||||
"networking": true,
|
||||
"request_messaging": [
|
||||
"net"
|
||||
],
|
||||
"grant_messaging": "all"
|
||||
}
|
||||
]
|
1
modules/terminal/package/my_directory/my_file.txt
Normal file
1
modules/terminal/package/my_directory/my_file.txt
Normal file
@ -0,0 +1 @@
|
||||
hello
|
BIN
modules/terminal/package/terminal.wasm
Normal file
BIN
modules/terminal/package/terminal.wasm
Normal file
Binary file not shown.
@ -81,12 +81,9 @@ impl Guest for Component {
|
||||
assert_eq!(our.process, ProcessId::Name("terminal".into()));
|
||||
print_to_terminal(1, &format!("terminal: start"));
|
||||
loop {
|
||||
let message = match receive() {
|
||||
let (source, message) = match receive() {
|
||||
Ok((source, message)) => {
|
||||
if our.node != source.node {
|
||||
continue;
|
||||
}
|
||||
message
|
||||
(source, message)
|
||||
}
|
||||
Err((error, _context)) => {
|
||||
print_to_terminal(0, &format!("net error: {:?}!", error.kind));
|
||||
@ -99,6 +96,10 @@ impl Guest for Component {
|
||||
ipc,
|
||||
..
|
||||
}) => {
|
||||
if our.node != source.node
|
||||
|| our.process != source.process {
|
||||
continue;
|
||||
}
|
||||
let Some(command) = ipc else {
|
||||
continue;
|
||||
};
|
||||
|
34
src/bootstrapping.md
Normal file
34
src/bootstrapping.md
Normal file
@ -0,0 +1,34 @@
|
||||
Bootstrapping the kernel
|
||||
|
||||
1. We have a bunch of packages that are built and zipped into "packages"
|
||||
|
||||
2. On startup, if we don't yet have a filesystem, we grab these packages by name from unix
|
||||
|
||||
3. For each package, "unzip" it and read the "manifest" file
|
||||
|
||||
4. For each entry in the manifest, start the named process by sending a message to kernel.
|
||||
|
||||
|
||||
```
|
||||
package.zip
|
||||
key_value.wasm
|
||||
key_value_worker.wasm
|
||||
index.html
|
||||
my_directory
|
||||
cool_image.png
|
||||
.manifest
|
||||
```
|
||||
|
||||
inside .manifest:
|
||||
(describes processes to start on-install)
|
||||
```
|
||||
[
|
||||
{
|
||||
"process_id": "key_value",
|
||||
"process_wasm": "key_value.wasm",
|
||||
"on_panic": {"on_panic": true},
|
||||
"networking": true,
|
||||
"messaging": ["vfs", "http_server", "http_bindings"]
|
||||
}
|
||||
]
|
||||
```
|
@ -15,6 +15,7 @@ pub async fn load_fs(
|
||||
home_directory_path: String,
|
||||
file_key: Vec<u8>,
|
||||
fs_config: FsConfig,
|
||||
vfs_message_sender: MessageSender,
|
||||
) -> Result<(ProcessMap, Manifest), FsError> {
|
||||
// load/create fs directory, manifest + log if none.
|
||||
let fs_directory_path_str = format!("{}/fs", &home_directory_path);
|
||||
@ -80,6 +81,7 @@ pub async fn load_fs(
|
||||
&kernel_process_id,
|
||||
&mut process_map,
|
||||
&mut manifest,
|
||||
&vfs_message_sender,
|
||||
)
|
||||
.await
|
||||
.expect("fresh bootstrap failed!");
|
||||
@ -88,56 +90,52 @@ pub async fn load_fs(
|
||||
Ok((process_map, manifest))
|
||||
}
|
||||
|
||||
// function run only upon fresh boot.
|
||||
// goes through /modules, gets their .wasm bytes, injects into fs and kernel state.
|
||||
/// function run only upon fresh boot.
|
||||
///
|
||||
/// for each folder in /modules, looks for a package.zip file, extracts the contents,
|
||||
/// sends the contents to VFS, and reads the manifest.json.
|
||||
///
|
||||
/// the manifest.json contains instructions for which processes to boot and what
|
||||
/// capabilities to give them. since we are inside runtime, can spawn those out of
|
||||
/// thin air.
|
||||
async fn bootstrap(
|
||||
our_name: &str,
|
||||
kernel_process_id: &FileIdentifier,
|
||||
process_map: &mut ProcessMap,
|
||||
manifest: &mut Manifest,
|
||||
vfs_message_sender: &MessageSender,
|
||||
) -> Result<()> {
|
||||
let names_and_bytes = get_processes_from_directories().await;
|
||||
const RUNTIME_MODULES: [&str; 8] = [
|
||||
"filesystem",
|
||||
"http_server",
|
||||
"http_client",
|
||||
"encryptor",
|
||||
"net",
|
||||
"vfs",
|
||||
"kernel",
|
||||
"eth_rpc",
|
||||
];
|
||||
let packages: Vec<zip::ZipArchive<std::io::Cursor<Vec<u8>>>> = get_zipped_packages().await;
|
||||
|
||||
let mut special_capabilities: HashSet<Capability> = HashSet::new();
|
||||
for (process_name, _) in &names_and_bytes {
|
||||
special_capabilities.insert(Capability {
|
||||
issuer: Address {
|
||||
node: our_name.to_string(),
|
||||
process: ProcessId::Name(process_name.into()),
|
||||
},
|
||||
params: "\"messaging\"".into(),
|
||||
});
|
||||
}
|
||||
for runtime_module in RUNTIME_MODULES {
|
||||
special_capabilities.insert(Capability {
|
||||
issuer: Address {
|
||||
node: our_name.to_string(),
|
||||
process: ProcessId::Name(runtime_module.into()),
|
||||
},
|
||||
params: "\"messaging\"".into(),
|
||||
});
|
||||
}
|
||||
// give all distro processes the ability to send messages across the network
|
||||
special_capabilities.insert(Capability {
|
||||
issuer: Address {
|
||||
node: our_name.to_string(),
|
||||
process: ProcessId::Name("kernel".into()),
|
||||
},
|
||||
params: "\"network\"".into(),
|
||||
});
|
||||
for package in packages {
|
||||
// for each file in package.zip, recursively through all dirs, send a newfile KM to VFS
|
||||
let mut stack = Vec::new();
|
||||
stack.push(package);
|
||||
|
||||
// for a module in /modules, put its bytes into filesystem, add to process_map
|
||||
for (process_name, wasm_bytes) in names_and_bytes {
|
||||
while let Some(mut package) = stack.pop() {
|
||||
for i in 0..package.len() {
|
||||
let mut file = package.by_index(i).unwrap();
|
||||
if file.name().ends_with('/') {
|
||||
let new_package = zip::ZipArchive::new(std::io::Cursor::new(file.into_inner())).unwrap();
|
||||
stack.push(new_package);
|
||||
} else {
|
||||
let file_path = file.sanitized_name();
|
||||
let mut file_content = Vec::new();
|
||||
file.read_to_end(&mut file_content).unwrap();
|
||||
let km = KernelMessage::NewFile {
|
||||
path: file_path,
|
||||
content: file_content,
|
||||
};
|
||||
vfs_message_sender.send(km).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get and read manifest.json
|
||||
|
||||
// for each process-entry in manifest.json:
|
||||
for entry in process_manifest {
|
||||
// save in process map
|
||||
let hash: [u8; 32] = hash_bytes(&wasm_bytes);
|
||||
|
||||
if let Some(id) = manifest.get_uuid_by_hash(&hash).await {
|
||||
@ -169,7 +167,43 @@ async fn bootstrap(
|
||||
entry.capabilities.extend(special_capabilities.clone());
|
||||
entry.wasm_bytes_handle = id;
|
||||
}
|
||||
|
||||
// spawn the requested capabilities
|
||||
|
||||
// spawn the granted capabilities
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const RUNTIME_MODULES: [&str; 8] = [
|
||||
"filesystem",
|
||||
"http_server",
|
||||
"http_client",
|
||||
"encryptor",
|
||||
"net",
|
||||
"vfs",
|
||||
"kernel",
|
||||
"eth_rpc",
|
||||
];
|
||||
|
||||
let mut runtime_caps: HashSet<Capability> = HashSet::new();
|
||||
for runtime_module in RUNTIME_MODULES {
|
||||
runtime_caps.insert(Capability {
|
||||
issuer: Address {
|
||||
node: our_name.to_string(),
|
||||
process: ProcessId::Name(runtime_module.into()),
|
||||
},
|
||||
params: "\"messaging\"".into(),
|
||||
});
|
||||
}
|
||||
// give all runtime processes the ability to send messages across the network
|
||||
runtime_caps.insert(Capability {
|
||||
issuer: Address {
|
||||
node: our_name.to_string(),
|
||||
process: ProcessId::Name("kernel".into()),
|
||||
},
|
||||
params: "\"network\"".into(),
|
||||
});
|
||||
|
||||
// finally, save runtime modules in state map as well, somewhat fakely
|
||||
for runtime_module in RUNTIME_MODULES {
|
||||
@ -178,9 +212,8 @@ async fn bootstrap(
|
||||
.or_insert(PersistedProcess {
|
||||
wasm_bytes_handle: 0,
|
||||
on_panic: OnPanic::Restart,
|
||||
capabilities: HashSet::new(),
|
||||
capabilities: runtime_caps.clone(),
|
||||
});
|
||||
entry.capabilities.extend(special_capabilities.clone());
|
||||
}
|
||||
|
||||
// save kernel process state. FsAction::SetState(kernel)
|
||||
@ -196,6 +229,33 @@ async fn bootstrap(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// go into /modules folder and get all
|
||||
async fn get_zipped_packages() -> Vec<zip::ZipArchive<std::io::Cursor<Vec<u8>>>> {
|
||||
let modules_path = std::path::Path::new("modules");
|
||||
|
||||
let mut packages = Vec::new();
|
||||
|
||||
if let Ok(mut entries) = fs::read_dir(modules_path).await {
|
||||
while let Ok(Some(entry)) = entries.next_entry().await {
|
||||
// get a file named package.zip
|
||||
if let Some(pkg) = entry.file_name().to_str() {
|
||||
if pkg == "package.zip" {
|
||||
// read the file
|
||||
if let Ok(bytes) = fs::read(entry.path()).await {
|
||||
// extract the zip
|
||||
if let Ok(zip) = zip::ZipArchive::new(std::io::Cursor::new(bytes)) {
|
||||
// add to list of packages
|
||||
packages.push(zip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return packages;
|
||||
}
|
||||
|
||||
async fn get_processes_from_directories() -> Vec<(String, Vec<u8>)> {
|
||||
let mut processes = Vec::new();
|
||||
|
||||
|
@ -192,6 +192,18 @@ impl UqProcessImports for ProcessWasi {
|
||||
// Ok(())
|
||||
}
|
||||
|
||||
async fn get_state(&mut self) -> Result<Option<Vec<u8>>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn get_state(&mut self, bytes: Vec<u8>) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn clear_state(&mut self) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn spawn(
|
||||
&mut self,
|
||||
id: wit::ProcessId,
|
||||
|
@ -378,6 +378,7 @@ async fn main() {
|
||||
home_directory_path.clone(),
|
||||
file_key,
|
||||
fs_config,
|
||||
vfs_message_sender.clone(),
|
||||
)
|
||||
.await
|
||||
.expect("fs load failed!");
|
||||
|
26
terminal/deps/random/insecure-seed.wit
Normal file
26
terminal/deps/random/insecure-seed.wit
Normal file
@ -0,0 +1,26 @@
|
||||
// https://github.com/bytecodealliance/wasmtime/blob/432b5471ec4bf6d51173def284cd418be6849a49/crates/wasi/wit/deps/random/insecure-seed.wit
|
||||
|
||||
/// The insecure-seed interface for seeding hash-map DoS resistance.
|
||||
///
|
||||
/// It is intended to be portable at least between Unix-family platforms and
|
||||
/// Windows.
|
||||
interface insecure-seed {
|
||||
/// Return a 128-bit value that may contain a pseudo-random value.
|
||||
///
|
||||
/// The returned value is not required to be computed from a CSPRNG, and may
|
||||
/// even be entirely deterministic. Host implementations are encouraged to
|
||||
/// provide pseudo-random values to any program exposed to
|
||||
/// attacker-controlled content, to enable DoS protection built into many
|
||||
/// languages' hash-map implementations.
|
||||
///
|
||||
/// This function is intended to only be called once, by a source language
|
||||
/// to initialize Denial Of Service (DoS) protection in its hash-map
|
||||
/// implementation.
|
||||
///
|
||||
/// # Expected future evolution
|
||||
///
|
||||
/// This will likely be changed to a value import, to prevent it from being
|
||||
/// called multiple times and potentially used for purposes other than DoS
|
||||
/// protection.
|
||||
insecure-seed: func() -> tuple<u64, u64>
|
||||
}
|
23
terminal/deps/random/insecure.wit
Normal file
23
terminal/deps/random/insecure.wit
Normal file
@ -0,0 +1,23 @@
|
||||
// https://github.com/bytecodealliance/wasmtime/blob/432b5471ec4bf6d51173def284cd418be6849a49/crates/wasi/wit/deps/random/insecure.wit
|
||||
|
||||
/// The insecure interface for insecure pseudo-random numbers.
|
||||
///
|
||||
/// It is intended to be portable at least between Unix-family platforms and
|
||||
/// Windows.
|
||||
interface insecure {
|
||||
/// Return `len` insecure pseudo-random bytes.
|
||||
///
|
||||
/// This function is not cryptographically secure. Do not use it for
|
||||
/// anything related to security.
|
||||
///
|
||||
/// There are no requirements on the values of the returned bytes, however
|
||||
/// implementations are encouraged to return evenly distributed values with
|
||||
/// a long period.
|
||||
get-insecure-random-bytes: func(len: u64) -> list<u8>
|
||||
|
||||
/// Return an insecure pseudo-random `u64` value.
|
||||
///
|
||||
/// This function returns the same type of pseudo-random data as
|
||||
/// `get-insecure-random-bytes`, represented as a `u64`.
|
||||
get-insecure-random-u64: func() -> u64
|
||||
}
|
23
terminal/deps/random/random.wit
Normal file
23
terminal/deps/random/random.wit
Normal file
@ -0,0 +1,23 @@
|
||||
// https://github.com/bytecodealliance/wasmtime/blob/432b5471ec4bf6d51173def284cd418be6849a49/crates/wasi/wit/deps/random/random.wit
|
||||
|
||||
package wasi:random
|
||||
|
||||
interface random {
|
||||
/// Return `len` cryptographically-secure pseudo-random bytes.
|
||||
///
|
||||
/// This function must produce data from an adequately seeded
|
||||
/// cryptographically-secure pseudo-random number generator (CSPRNG), so it
|
||||
/// must not block, from the perspective of the calling program, and the
|
||||
/// returned data is always unpredictable.
|
||||
///
|
||||
/// This function must always return fresh pseudo-random data. Deterministic
|
||||
/// environments must omit this function, rather than implementing it with
|
||||
/// deterministic data.
|
||||
get-random-bytes: func(len: u64) -> list<u8>
|
||||
|
||||
/// Return a cryptographically-secure pseudo-random `u64` value.
|
||||
///
|
||||
/// This function returns the same type of pseudo-random data as
|
||||
/// `get-random-bytes`, represented as a `u64`.
|
||||
get-random-u64: func() -> u64
|
||||
}
|
183
terminal/uqbar.wit
Normal file
183
terminal/uqbar.wit
Normal file
@ -0,0 +1,183 @@
|
||||
package component:uq-process
|
||||
|
||||
interface types {
|
||||
// JSON is passed over WASM boundary as a string.
|
||||
type json = string
|
||||
|
||||
// context is a string of UTF-8 JSON.
|
||||
// it is used when building a Request to save information
|
||||
// that will not be part of a Response, in order to more
|
||||
// easily handle ("contextualize") that Response.
|
||||
type context = json
|
||||
|
||||
variant process-id {
|
||||
id(u64),
|
||||
name(string),
|
||||
}
|
||||
|
||||
// TODO better name for this
|
||||
record address {
|
||||
node: string,
|
||||
process: process-id,
|
||||
}
|
||||
|
||||
record payload {
|
||||
mime: option<string>,
|
||||
bytes: list<u8>,
|
||||
}
|
||||
|
||||
record request {
|
||||
// if true, this request inherits context AND payload of incipient
|
||||
// request, and cannot have its own context.
|
||||
inherit: bool,
|
||||
// if Some, this request expects a response in the number of seconds given
|
||||
expects-response: option<u64>,
|
||||
ipc: option<json>,
|
||||
metadata: option<json>,
|
||||
// to grab payload, use get_payload()
|
||||
}
|
||||
|
||||
record response {
|
||||
ipc: option<json>,
|
||||
metadata: option<json>,
|
||||
// to grab payload, use get_payload()
|
||||
}
|
||||
|
||||
// a message can be a request or a response.
|
||||
// within a response, there is a result which surfaces any error
|
||||
// that happened because of a request.
|
||||
// a successful response will contain the context of the request
|
||||
// it matches, if any was set.
|
||||
variant message {
|
||||
request(request),
|
||||
response(tuple<response, option<context>>),
|
||||
}
|
||||
|
||||
variant capabilities {
|
||||
none,
|
||||
all,
|
||||
some(list<signed-capability>),
|
||||
}
|
||||
|
||||
record signed-capability {
|
||||
issuer: address,
|
||||
params: json,
|
||||
signature: list<u8>,
|
||||
}
|
||||
|
||||
// network errors come from trying to send a message to another node.
|
||||
// a message can fail by timing out, or by the node being entirely unreachable (offline).
|
||||
// in either case, the message is not delivered, and the process that sent it
|
||||
// receives that message along with any assigned context and/or payload,
|
||||
// and is free to handle it as it sees fit.
|
||||
// note that if the message is a response, the process can issue a response again,
|
||||
// and it will be directed to the same (remote) request as the original.
|
||||
record send-error {
|
||||
kind: send-error-kind,
|
||||
message: message,
|
||||
payload: option<payload>,
|
||||
}
|
||||
|
||||
enum send-error-kind {
|
||||
offline,
|
||||
timeout,
|
||||
}
|
||||
|
||||
// on-panic is a setting that determines what happens when a process panics.
|
||||
// NOTE: requests should have expects-response set to false, will always be set to that by kernel
|
||||
variant on-panic {
|
||||
none,
|
||||
restart,
|
||||
requests(list<tuple<address, request, option<payload>>>),
|
||||
}
|
||||
}
|
||||
|
||||
world uq-process {
|
||||
use types.{
|
||||
json,
|
||||
context,
|
||||
address,
|
||||
process-id,
|
||||
|
||||
payload,
|
||||
request,
|
||||
response,
|
||||
message,
|
||||
|
||||
capabilities,
|
||||
signed-capability,
|
||||
|
||||
send-error,
|
||||
send-error-kind,
|
||||
on-panic,
|
||||
}
|
||||
|
||||
// entry point to all programs
|
||||
export init: func(our: address)
|
||||
|
||||
// system utils:
|
||||
|
||||
import print-to-terminal: func(verbosity: u8, message: string)
|
||||
import get-unix-time: func() -> u64
|
||||
import get-eth-block: func() -> u64
|
||||
|
||||
// process management:
|
||||
|
||||
import set-on-panic: func(on-panic: on-panic)
|
||||
|
||||
import get-state: func() -> option<list<u8>>
|
||||
|
||||
import set-state: func(bytes: list<u8>)
|
||||
|
||||
import clear-state: func()
|
||||
|
||||
import spawn: func(id: process-id, %package: string, full-path: string, on-panic: on-panic, capabilities: capabilities) ->
|
||||
option<process-id>
|
||||
|
||||
// capabilities management
|
||||
|
||||
// gives us all our signed capabilities so we can send them to others
|
||||
import get-capabilities: func() -> list<signed-capability>
|
||||
|
||||
// gets a single specific capability
|
||||
import get-capability: func(issuer: address, params: json) -> option<signed-capability>
|
||||
|
||||
// attaches a specific signed capability to our next message
|
||||
import attach-capability: func(capability: signed-capability)
|
||||
|
||||
// saves capabilities to our store, so we can use them
|
||||
import save-capabilities: func(capabilities: list<signed-capability>)
|
||||
|
||||
// check to see if the sender of a prompting message has a given capability, issued by us
|
||||
// if the prompting message has a remote source, they must have attached it.
|
||||
import has-capability: func(params: json) -> bool
|
||||
|
||||
|
||||
// message I/O:
|
||||
|
||||
// ingest next message when it arrives along with its source.
|
||||
// almost all long-running processes will call this in a loop
|
||||
import receive: func() -> result<tuple<address, message>, tuple<send-error, option<context>>>
|
||||
|
||||
// gets payload, if any, of the message we just received
|
||||
import get-payload: func() -> option<payload>
|
||||
|
||||
// send message(s) to target(s)
|
||||
import send-request:
|
||||
func(target: address, request: request, context: option<context>, payload: option<payload>)
|
||||
import send-requests:
|
||||
func(requests: list<tuple<address, request, option<context>, option<payload>>>)
|
||||
import send-response:
|
||||
func(response: response, payload: option<payload>)
|
||||
|
||||
// send a single request, then block (internally) until its response
|
||||
// the type is Message but will always contain Response
|
||||
import send-and-await-response:
|
||||
func(target: address, request: request, payload: option<payload>) ->
|
||||
result<tuple<address, message>, send-error>
|
||||
|
||||
// wasi
|
||||
import wasi:random/insecure
|
||||
import wasi:random/insecure-seed
|
||||
import wasi:random/random
|
||||
}
|
@ -125,6 +125,12 @@ world uq-process {
|
||||
|
||||
import set-on-panic: func(on-panic: on-panic)
|
||||
|
||||
import get-state: func() -> option<list<u8>>
|
||||
|
||||
import set-state: func(bytes: list<u8>)
|
||||
|
||||
import clear-state: func()
|
||||
|
||||
import spawn: func(id: process-id, %package: string, full-path: string, on-panic: on-panic, capabilities: capabilities) ->
|
||||
option<process-id>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user