A framework for creating plugins for the Taubyte WebAssembly Virtual Machine
Plugins are fashioned from structures that bear method names prefixed with W_. The signature parameters can be inclusive of the following:
- context
- module
- uint, uint8, uint16, uint32, uint64
- int, int8, int16, int32, int64
In these parameters:
- The
module
parameter enables reading and writing to/from the module memory. - The
context
andmodule
parameters, although optional, must be in the first and second positions respectively, if included.
type tester struct{}
import "github.com/taubyte/vm-orbit/satellite"
func (t *tester) W_atoiAdd(ctx context.Context, module satellite.Module, stringPtr uint32, stringLen uint32, addVal int,resPtr uint32) int {
data, err := module.MemoryRead(stringPtr, stringLen)
if err != nil {
return 1
}
parsedVal, err := strconv.Atoi(string(data))
if err != nil {
return 1
}
sum := parsedVal + addVal
sumString := fmt.Sprintf("added sum: %d",sum)
n, err := module.MemoryWrite(resPtr, []byte(sumString))
if err != nil{
return 1
}
return 0
}
func (t *tester) W_sum(a, b int64) int64 {
return a + b
}
Because it is bases on the Hashicorp Plugin System, plugins are designed to be compiled as binaries to be incorporated for usage in the Taubyte VM. To achieve this, a main()
function needs to be defined in a main package. By employing the plugin.Serve
function, the structured plugin methods can be exported.
package main
import "github.com/taubyte/vm-orbit/plugin"
func main() {
plugin.Serve("testing", &tester{})
}
Then Built with:
go build
- This binary can be referenced with shape deployment via spore-drive
Dreamland is a tool used to create a local taubyte network. Plugins can be tested using dreamland before deploying to a production network.
Start Dreamland
dream new multiverse
Once Network has been started (after SUCCESS Universe <name-of-universe> started!
), inject plugin from a new terminal
dream inject attachPlugin -p <path/to/plugin/binary>
Connect to the Taubyte Web Console, Select "Dreamland" as a network.
Next, Create a DFUNC. It is advisable to set up an HTTP GET function for rapid testing, using a generated domain.
To call this HTTP DFunc, you will need to add the generated FQDN to /etc/host as 127.0.0.1. You will also need to append the port that the Substrate(node) protocol is running on to the URL. Retrieve this port by executing:
dream status node
After these two steps, accessing http://<generated-fqdn>:<node-port>/<function-path>
will execute your function. Note that you'll need to use http
as dreamland
does not support https
as of today.
vm-orbit/tests/suite
allows for creating local plugin tests swiftly. For testing, the following are required:
- Plugin Binary
- Wasm File with an exported method calling your plugin method
Suite provides helper functions to generate these.
Firstly, create a builder for the language you are using to generate your Wasm file and plugin:
import goBuilder "github.com/taubyte/vm-orbit/tests/suite/builders/go"
builder := goBuilder.New()
pluginPath, err := builder.Plugin("path/to/plugin")
To build the Wasm file, you'll need to write code files that appropriately reference your plugins. Refer to: dFunc. Then use the builder to create the Wasm file:
Then used the builder to generate the wasm file
wasmPath, err := builder.Wasm(context.Background(), ...list-of-Files-To-Include)
import "github.com/taubyte/vm-orbit/tests/suite"
testingSuite, err := suite.New(context.Background)
err := testingSuite.AttachPluginFromPath("path/to/plugin")
module, err := testingSuite.WasmModule("path/to/wasmFile")
ret, err := module.Call(context.Background(),"functionName")
Example:
//go:wasm-module moduleName
//export writeSize
func writeSize(*uint32)
//go:wasm-module moduleName
//export writeName
func writeName(*byte)
//export dFunc
func dFunc() {
var size uint32
writeSize(&size)
nameData := make([]byte, size)
writeName(&nameData[0])
name := string(nameData)
}
- The comments before the function declarations are required
- the
//go:wasm-module
comment gives a reference to the name of the wasm module of the plugin- Example:
package main import "github.com/taubyte/vm-orbit/plugin" func main() { // methods of helloWorlder will be exported to the module "helloWorld" plugin.Export("helloWorld", &helloWorlder{}) }
- Here the plugin module name is
helloWorld
- The name of the function is the name of your structure's method minus
W_
- Example:
func (t *helloWorlder) W_helloSize(ctx context.Context, module satellite.Module, sizePtr uint32) uint32 { if _, err := module.WriteStringSize(sizePtr, helloWorld); err != nil { return 1 } return 0 }
- Here the name of this method would be
helloSize
A good rule of thumb is if a value needs to be read from memory or written to the signature value needs to be a pointer. If the value is interpreted a raw value should be passed.
Uints, Floats, and Ints and their pointers are supported types for the signature
Any data more complex than these types are interpreted as []byte which must be read or written to in memory.
Any data more complex than []byte is encoded to []byte through the use of helper methods, already available in an orbit module. Example:
import "github.com/taubyte/vm-orbit/satellite"
func (h *helloWorlder) W_readWrite(
ctx context.Context,
module satellite.Module,
stringSlicePtr,
stringSliceSize,
){
stringSlice, err := module.ReadStringSlice(stringSlicePtr,stringSliceSize)
}
More encoding methods can be found at github.com/taubyte/go-sdk/utils/codec
.
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
export PATH="$PATH:$(go env GOPATH)/bin"
cd <path/to/vm-orbit>
go generate ./...
This project is licensed under the BSD 3-Clause License. For more details, see the LICENSE file.
Find us on our Discord