Powerbox and Foreign Function Interface

Content moved to: Node Powerbox

Existing features

Tuplespace architecture

     +-------------------+
     | Virtual Machine / |
     |   Interpreter     |
     +---^----------+----+
         |          |
         |          |
     +---+----------v----+
     |    Tuplespace     |
     +---^----------+----+
         |          |
         |          |
       +-+----------v-+
       | Native  code |
       +-^----------+-+
         |          |
         |          |
+--------+----------v-------------+
| Network / File system / Console |
+---------------------------------+

All interaction with other processes, whether they be Rholang processes or native code, happens through the tuplespace.  The VM / interpreter is the main client of the tuplespace: the interpretation of

    x!(Q)

is that the code of the process Q is stored at the key x, while the interpretation of

    for (y <- x) { P }

is that the continuation

    (y) => P

is stored at the key x, where P is Rholang code.

There are other clients, however.  In order to cause any side effects on the computer, it is necessary for the node to store continuations as well, but the continuations are usually compiled Scala code that unmarshals Rholang data into Scala data and processes it.  This Scala code can print to the console or send messages over the network, or write to a file, etc.  When the operation is complete, the Scala code can then marshal the result and send it on a channel.

Method syntax

The spec allows for invoking methods on processes.  There is currently (2018-03-14) no way to declare methods, but we can expose methods on processes that represent class objects or instance objects.

Interfaces

Synchronous Interface

Any synchronous code may be exposed as a process with methods.  Method invocations must not occur until the invocation is at the top level.  Method invocation is eager.  Methods must not block.  Invoking a method will unmarshal the arguments, invoke a method on the object, and marshal the result; the result will be marshaled and will effectively replace the original invocation expression in the term.

Asynchronous Interface

Asynchronous code must be exposed either as a method that schedules an asynchronous side effect and immediately returns Nil, or as a native process listening on a channel.  When the asynchronous operation is complete, the native callbacks may marshal any results and use a produce() call to send them back to Rholang code on some channel.

FFI API sketch

Resolvers

A resolver takes a string and returns a process.  Examples of resolvers include

  • directories in a file system, where the string is a path and the returned process represents a file or directory
  • an HTTP client, where the string is a URL and the returned process is the content (the mime type determines what kind of content)
  • a TCP client, where the string is a port to bind to; the resulting process is a resolver where the string is the IP address and port to connect to, etc.

Resolvers may be synchronous or asynchronous; an asynchronous resolver must also take a return channel for the resulting process to be returned on.  Asynchronous resolvers may take an error channel as well.

Powerbox

The powerbox process will be injected into every Rholang system contract as its first argument.  The powerbox process will have some methods similar to the following:

  • fileSystem(): Directory (subclass of Resolver)
  • httpClient(): Resolver
  • stdout(): File
  • stdin(): File
  • stderr(): File
  • tcpClient(): Resolver
  • load(jar: File, className: String): Process
  • etc.

The load() method takes a File process and a class name and returns the corresponding marshaled class object.  Since there is no way for the loader to know whether the code is synchronous or asynchronous, the marshaled class object should expose both synchronous and asynchronous interfaces.  Given a Java method of the form

    ReturnType methodName(T1 arg1, T2 arg2, ..., Tn argn)

the FFI should expose a synchronous method of the same signature and a method methodNameAsync() that takes a return channel in the first position and the rest of the arguments afterwards.