/vm-orbit

Primary LanguageGoBSD 3-Clause "New" or "Revised" LicenseBSD-3-Clause

vm-orbit

A framework for creating plugins for the Taubyte WebAssembly Virtual Machine

Creating A Plugin

Exported Functions

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 and module parameters, although optional, must be in the first and second positions respectively, if included.

Example

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
}

Building The Plugin

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.

Example

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

Testing

Dreamland is a tool used to create a local taubyte network. Plugins can be tested using dreamland before deploying to a production network.

Dreamland

Inject Plugin

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>

Create a Dfunc on Web Console

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.

Go Test

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.

Using the Builder For Plugins and Wasm

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()

Build Your Plugin

	pluginPath, err := builder.Plugin("path/to/plugin")

Build Your Wasm File

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)

Using the Suite to Test Plugins

Create a Testing Suite

import "github.com/taubyte/vm-orbit/tests/suite"

testingSuite, err := suite.New(context.Background)

Attach a Plugin onto the Suite

err := testingSuite.AttachPluginFromPath("path/to/plugin")

Attach the Wasm File to the Suite, and Get the Module

module, err := testingSuite.WasmModule("path/to/wasmFile")

Call the Dfunc

ret, err := module.Call(context.Background(),"functionName")

Creating a Dfunc with reference to a Plugin

Go

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

Understanding The Signature in the dFunc

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.

Building Proto

Install go protoc gen

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"

Build

cd <path/to/vm-orbit>
go generate ./...

License

This project is licensed under the BSD 3-Clause License. For more details, see the LICENSE file.

Help

Find us on our Discord