experiment: wasm execution engine - #5584
Draft
AuHau wants to merge 3 commits into
Draft
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is just an experiment, not meant to be merged! Hence, only a draft PR.
tldr: I created a POC with a WASM runtime embedded in Bee. Modules execute locally. See Demo section at bottom for examples.
Motivation
During my Swarm days, I had this idea: what if there was a layer on top of Bee's DISC that would allow computation and interaction with DISC? Even if it would only be local execution on "your node", this would allow basically implementing all the higher-level protocols of Swarm like Manifests, Feeds, etc with it. Each protocol would have its own implementation module that would be uploaded to the network and, upon the user's request, downloaded and executed locally. This would add great flexibility in how these primitives would compose, iterate, and integrate.
I discussed this idea with @zelig and discovered that he has a similar idea. One overlap between our ideas was adding a safe execution engine. He originally mentioned using something like the V8 JavaScript engine, so I wanted to explore some areas around:
This PR, and especially these notes, are what came out of this exploration.
My plan was simple. To include the execution engine in Bee and add an API call that lets you locally execute a content-addressed module uploaded in the Swarm network. This later expanded a bit into exploring how HTTP and browsers could integrate with the execution engine.
Engine
I started by deciding which engine to integrate. @zelig suggested originally V8 JavaScript engine. Quickly, I ran into the fact that to include that directly into Bee, its build setup would have to be tweaked to enable
CGO_ENABLED, which is currently disabled. I assume that is not desired. Later, I will explain why it might not really be a blocker, though.But even if we could include it, I am not sure JavaScript is the right tool for this task. It has a large footprint and might be unnecessarily complex to embed in Bee as an execution engine. I believe a tool that exists exactly for this purpose is WASM. While this technology is not that big on the web itself (it is mainly used as optimization modules for compute-heavy use cases like graphics, etc), it is used on the "backends," for example, "serverless" function execution, plugin systems, and similar. It was built to be fast, safe, compact, and portable, which fits our scope well. I decided to continue with WASM.
Integration and security
WASM is designed to be a sandboxed environment, but even with that goal, leaks and exploits can still happen, so I looked for an architectural solution to improve security with a defense-in-depth approach. The obviously naive approach to integrate the engine is to rely on the WASM sandbox itself for security, include it directly in the codebase, and run it inside the
beeprocess. A more complex solution would be to use the same concept that Chrome uses for isolation of website's namespaces - running the engine in an isolated process outside ofbeeprocess. Since Bee manages its own wallet, this seems like a good idea.For an out-of-process approach, there are at least two ways it could work.
The first option is that
beewould include the engine code, and uponbeestartup, it would spawn itself again with a special flag to initialize for "WASM-only support" and an IPC channel between the two processes.The second option is to create a dedicated helper that wouldn't be limited by a language or other restrictions in the
beecodebase (for example,CGO_ENABLED). This binary could then be shipped directly in thebeebinary using Go'sembedmechanism. On the one hand, this would require more complex handling, as the embedded binary would need to be extracted somewhere, but it would also offer greater flexibility and solve the two-binary packaging issue. We already have Bee'sDATA_DIR, so we have a place where the WASM binary could live. Moreover, this engine binary probably won't be very big, so the total size of Bee's package shouldn't grow much.There are also additional mechanisms that would improve the security hardening at the OS level for the out-of-process approach. They are platform-specific, though, which would introduce a maintenance burden, but to list a few:
When looking at the WASM engines, two options stood out:
This leads to another consideration: capping execution time. Computation time is an obviously constrained resource that needs to be protected similarly to storage and bandwidth. Untrusted code can be malicious and could, for example, incorporate an infinite loop. A simple, naive approach is to add a watchdog that kills execution upon timeout. A more robust approach is fuel metering, which assigns a cost to each operation and tracks it over execution, with a budget set for each executed module. If the approach for execution in the engine should be changed from "local level" (approach of my POC - call API and compute something on your node) into "network level" (for example module that will dynamically route content in the network - hence a module that might be run in the whole network) - then fuel metering becomes a critical feature in order to not overload the network.
If this work should continue and aim for production release, I would suggest using the out-of-process approach with a Rust helper that uses the
wasmtimeengine. More thought would have to be put into designing an efficient IPC channel, as there might be intensive data transfers between these two points.For my POC, for the sake of simplicity, I decided to use the
wazeroengine directly incorporated into thebeecode.This POC
This PR includes a POC that incorporates the Wazero WASM engine and an API route
/@/<ref>, which allows execution of a WASM module that is uploaded under the address<ref>.This alone doesn't allow anything interesting, as the module has no input or output. I therefore extended support for input data first to the HTTP request body, and later to include the request's method, body, and headers (only a limited subset). Then I added support for setting the body, headers, and status code for the HTTP response. This let me basically create an HTTP server inside the engine. This is demonstrated in one of the examples I created:
webapp.Later on, I explored how to expose Bee's internal functionality or potentially helpers inside the engine and created the following host module and functions that allow fetch/upload data in Swarm:
bytesGet()/chunkGet()- for fetching databytesPut()/chunkPut()- for uploading dataexecuteModule()- invocation of another module allowing module compositionsThere is another example related to encryption/content metadata dispatching which demonstrates the module composition using
executeModule()that I created:dispatch.For more details about the POC implementation there are some notes here:
/@/endpointswarmmodule ABIDevX
How will devs interact with such an execution engine? That is also something I set out to explore.
One advantage of using WASM is that many languages support it. So potential developers have rather big flexibility around what tools and what languages to use. On the other hand, as the execution environment has its own specifications around exposed host functions and helpers, it is actually beneficial to have dedicated SDKs that will provide bindings and potentially other helper functions.
I created one such SDK for AssemblyScript (TypeScript but for WASM) called
bee-wasm-js. It incorporates a few helpful functionalities:Demo
Example of a very simple module built with
bee-wasm-js:If you want to run it, then
make binaryof this branch, and run a node at least in light mode.To build it, upload it and execute it:
I also built two more elaborate examples:
dispatch- using the sub-execution of another module with theexecuteModule()callwebapp- backend HTTP serverResources
Disclaimer
AI was used in this work. I reviewed part of the
bee's initial groundwork, but then relaxed my oversight, as this is really an exploratory POC not meant to be merged in.