A Rholang tutorial
Rholang is a new programming language designed for use in distributed systems. Like all newborn things, it is growing and changing rapidly; this document describes the syntax that will be used in the RNode-0.3 release.
Rholang is "process-oriented": all computation is done by means of message passing. Messages are passed on "channels", which are rather like message queues but behave like sets rather than queues. Rholang is completely asynchronous, in the sense that while you can read a message from a channel and then do something with it, you can't send a message and then do something once it has been received---at least, not without explicitly waiting for an acknowledgment message from the receiver. Note that throughout this document the words "name" and "channel" are used interchangeably. This is because in the rho-calculus (on which Rholang is based) the term name is used, however because you can send and receive information on names, semantically they are like channels.
There is not an IDE for Rholang. Get started with Rholang by selecting one of the options below.
- Run Rholang on RNode - Write Rholang contracts in an editor of your choice and run them on RNode using either the REPL or EVAL modes. Get started with the latest version of RNode.
- Run Rholang on a web interface - This web interface was created by a RChain community member.
- Write Rholang using an IntelliJ plugin - This Rholang IntelliJ plugin was created by a RChain community member.
Contracts and sending data#
1-2) To create a new, private channel, we use the
new ... in construction. No other process can send or receive messages over this channel unless we explicitly send this channel to the other process. This process starts by creating a new name
HelloWorld and then running a contract on it. The
contract production creates a process that spawns a copy of its body whenever it receives a message.
3) On the return channel we send a process, which is the string
6) We send the channel
myChannel to the contract at
* operator "unquotes" a channel to get its underlying process. In Rholang you can only send processes over channels; you cannot send channels over channels directly. Therefore, we use
* to turn the private channel into a process prior to sending.
2) Contracts take at least one parameter, but we can throw it away by binding it to the wildcard
3) We create a new channel
4) We send the string process
"Hello again, world!" over the new channel.
5) We listen on the new channel for a single message. The
for operation blocks until there's a message available on the channel
chan. In Rholang you can only receive names on channels (note that this differs from sending!). The binding on the left side of the
<- in the
for is actually a name pattern. In this example the pattern is
@text, which means the name being received is a quoted process and we want to bind that process to the free variable
for operation is just like a contract except that it only reads one message and then becomes its body instead of forking a copy of its body for each message. In this case we choose to do nothing in the
for body by simply making it the stopped process
Nil, however in principle we would want to proceed with some further processing of the
text contained in
7) We trigger the contract.
1) We create a new channel MakeCell and then use it on line 3 as the name of an internal contract. No process other than the code inside this lexical scope can invoke it.
MakeCell contract takes three arguments. The first argument is the initial value to be stored in the cell. The second and third arguments are channels over which the cell will receive requests to get and set the value. Note that we want the first argument to be a process and the second and third to be names, but names are always received over channels so we need to make the first argument a pattern beginning with
@ to indicate that the name we receive as the first argument is a quoted process and it is that process which we want to bind to the variable.
4) To store the value, we create a new channel. This channel will have at most one message on it containing the current value of the cell.
5) Before this line, there are no messages on the
valueStore channel. After we send the initial value, it is the only value on that channel.
6) We set up a contract to listen on the
get channel. Each time a message is sent on
get, the body of the contract will be executed
7) We block until we get one message from the
valueStore channel. Because there is at most one message ever waiting on
valueStore, reading the message behaves much like acquiring a lock.
8) We send the current value on
valueStore again, allowing other messages to be processed (i.e. releasing the lock), and we send the current value back to the client on the
11) In concurrently with the
get contract, we run a contract listening on
12) We block until there's a message on
valueStore, then read it. We throw away the message that we read.
13) We send the new value to store on
valueStore and signal that the operation is complete.
18-36) The usage code demonstrates creating a cell, assigning the initial value 123, getting that value, setting the value to 456, then getting that value.
Note the deep layers of callback. Rholang was designed to make concurrent computations natural to express; as a consequence, data dependencies implicit in sequencing in other languages must be made explicit.
Iteration and matching#
In the code below, we show an example of iterating through a list.
match construction allows destructuring a variable through pattern matching.
4) List patterns support matching the remainder of a list. If
list matches the pattern of a head/tail pair then we execute the main body of the loop.
5) We create a channel for the item handler to notify us that it's done with the current item.
6) We invoke the processor on the item and the acknowledgement channel.
7) When we receive acknowledgement, we reinvoke the iterator on the tail.
10) If the list is empty, we signal that the processing is complete.
14) We invoke the iterator.
contract gets invoked for each item in the list. On line 17, we tell the iterator that we're done with this item.
for contains the code that should be executed when the interation is complete.
2) One design pattern, used in the MakeCell contract above, is to receive from the caller a channel for each different piece of functionality that a process provides. An object-oriented programmer might say that MakeCell requires the caller to provide a channel for each method. MakeCoatCheck uses a more object-oriented approach, as we'll see.
3) We create a
port channel for interacting with the coat check as well as a
table name which will be used in storing/retrieving values in the coat check.
4) We send
port out to the caller, so they can interact with the coat check.
5, 11, 17) We define different methods which can be called by sending a message on
port. This is done by specifying mutually exclusive patterns the message on
port can match, with the first element of the message being the method name and subsequent elements being the argument(s) and return channel. Using the
<= arrow instead of the
<- arrow means that the
fors are "replicated". This gives them the same behavior as
contracts, i.e. the process listening for messages on
port persists after spawning an instance of its body.
8) We take advantage of being able to quote any process to make a name in order to create a unique name for each value to be stored at. The process
*ticket | *table is produced by the concurrent composition of the processes produced by unquoting the names
table. That process can then be quoted to form a unique name that is then used to store the value by sending it on the name.
Dining philosophers and deadlock#
The dining philosophers problem has two philosophers that share only one set of silverware. Philosopher1 sits on the east side of the table while Philosopher2 sits on the west. Each needs both a knife and a spoon in order to eat. Each one refuses to relinquish a utensil until he has used both to take a bite. If both philosophers reach first for the utensil at their right, both will starve: Philosopher1 gets the knife, Philosopher2 gets the spoon, and neither ever lets go.
Here's how to solve the problem:
4, 9) The join operator, denoted with a semicolon
;, declares that the continuation should only proceed if there is a message available on each of the channels simultaneously, preventing the deadlock above.
There are three hashing functions available:
Hashing functions are exposed as channels which expect two arguments:
- byte array to hash
- return channel for sending back the hash represented as byte array
Let's hash a rholang program and print out it in base16. In rholang:
This will print the hash of our program
We need a pair of keys; let's generate some with
Ed25519, available in the project. In the scala console, we enter the following:
Now we need to sign the hash we obtained in first step. First we convert the hexadecimal strings we printed earlier back into byte arrays, then sign the result:
Now we can pass the signature and public key to our rholang program to verify it using the available crypto functions.
ed25519Verifychannel expects four arguments as follows:
- data to verify. In our case, this will be the keccak256 hash of our rholang program. The hash is represented in base16, so we need to call
hexToByteson it to turn the string into byte array
- signature. Again, we have hexadecimal string, so we need to turn it into a byte array with
- public key. This is the public key corresponding to the private one used to issue the signature.
- channel on which the result of verification will be returned.
So, in rholang we run:
and we should see:
which means that our signed hash is verified.
If we, for example, pass in a corrupted hash, changing the initial 'a' to a 'b':
we will get:
- data to verify. In our case, this will be the keccak256 hash of our rholang program. The hash is represented in base16, so we need to call
Secure design patterns#
In this section we describe several design patterns. These patterns are adapted from Marc Stiegler's A PictureBook of Secure Cooperation.
In the MakeCell contract, the client provides two channels, one for getting the value and one for setting it. If the client then passes only the
get channel to another process, that process effectively has a read-only view of the cell.
set are called "facets" of the process. They encapsulate the authority to perform the action. If the
set channel is a public channel like
@"Foo", then anyone who can learn or even guess the string
"Foo" has the authority to set the cell's value. On the other hand, if the
set channel was created with the
new operator, then there's no way for any other process to construct the
set channel; it must be passed to a process directly in order for the process to use it.
Note that possession of
set is also authority to intercept messages sent to the cell:
This term has two processes listening on the channel
get and a single message sent over
get. Only one of the two processes will be able to receive the message.
By receiving channels from the client for getting and setting, the MakeCell contract is leaving the decisions about how public those channels are to the client. The MakeCoatCheck contract, on the other hand, constructs its own channels and exposes methods the client, so it is in a position to enforce privacy guarantees.
In the MakeCoatCheck contract, there's only one channel and messages are dispatched internally. To get the same effect as a read-only facet, we can create a forwarder process that simply ignores any messages it doesn't want to forward. The contract below only forwards the "get" method.
We can implement revocation by creating a forwarder with a kill switch.
3) We create a port to listen for method calls and a channel
forwardFlag to store whether to forward messages.
4) We return the channel on which clients send requests and the channel on which to send the kill signal.
5) We set the initial state of
forwardFlag to true.
6-11) We read in an arbitrary message, get and replace the value of the flag. If the flag is true, we forward the message to
12-14) If a message is ever sent on the
kill channel, we set
forwardFlag to false. The forwarder process then stops forwarding messages.
By combining an attenuating forwarder with a revokable forwarder, we get both features:
A logging forwarder can record all messages sent on a channel by echoing them to a second channel.
Suppose Alice has a channel and would like to log Bob's access to it. Bob would like to delegate the use of that channel to Carol and log her access. Each party is free to construct their own logging forwarder around the channel they have received. Alice will hold Bob responsible for whatever Carol does.
Sealing and unsealing#
A sealer/unsealer pair gives the same functionality as public keys, but without cryptography. It's merely an attenuation of the coat check described above. This design pattern can be used to sign something on a user's behalf. In the Rholang blockchain tutorial, we'll see that a sealer/unsealer pair even works as a signing/verification pair of keys on the blockchain because there are no secrets to store, only unforgeable names to be kept inaccessible.
Beware of sending attenuators#
A basic principle to keep in mind with RChain processes is one that is similar to more traditional web applications: whatever code you send to another party can be disassembled. Ever since the late 1990s when buying things over the web became possible, there have been e-commerce platforms where the platform relied on the users' browsers to send the correct price of the item back to it. The authors didn't think about the user opening the developer tools and changing the price before it got sent back. The right way to build an e-commerce platform is to store the prices on the server and check them there.
Suppose that Bob is willing to run some code for Alice; he has a contract that says something like, "Get a process from this channel and run it."
RChain is a language designed for use on a blockchain, but we have not mentioned anything about nodes, namespaces, wallets, Rev and phlogiston, network structure, or Casper. A forthcoming document will address all these issues and more.
We hope that the foregoing examples spark a desire to write more code and demonstrate the ease of expressing concurrent designs.