Nodes and Workers
Ockam Nodes and Workers decouple applications from the host environment and enable simple interfaces for stateful and asynchronous message-based protocols.
Last updated
Was this helpful?
Ockam Nodes and Workers decouple applications from the host environment and enable simple interfaces for stateful and asynchronous message-based protocols.
Last updated
Was this helpful?
At Ockam’s core are a collection of cryptographic and messaging protocols. These protocols make it possible to create private and secure by design applications that provide end-to-end application layer trust it data.
Ockam is designed to make these powerful protocols easy and safe to use in any application environment, from highly scalable cloud services to tiny battery operated microcontroller based devices.
However, many of these protocols require multiple steps and have complicated internal state that must be managed with care. It can be quite challenging to make them simple to use, secure, and platform independent.
Ockam help hide this complexity and decouple from the host environment - to provide simple interfaces for stateful and asynchronous message-based protocols.
An Ockam Node is any program that can interact with other Ockam Nodes using various Ockam Protocols like Ockam and Ockam Secure Channels.
Using the Ockam Rust crates, you can easily turn any application into a lightweight Ockam Node. This flexible approach allows your to build secure by design applications that can run efficiently on tiny microcontrollers or scale horizontally in cloud environments.
Rust based Ockam Nodes run very lightweight, concurrent, stateful actors called Ockam . Using Ockam Routing, a node can deliver messages from one worker to another local worker. Using Ockam Transports, nodes can also route messages to workers on other remote nodes.
A node requires an asynchronous runtime to concurrently execute workers. The default Ockam Node implementation in Rust uses tokio
, a popular asynchronous runtime in the Rust ecosystem. We also support Ockam Node implementations for various no_std
embedded targets.
The first thing any Ockam rust program must do is initialize and start an Ockam node. This setup can be done manually but the most convenient way is to use the #[ockam::node]
attribute that injects the initialization code. It creates the asynchronous environment, initializes worker management, sets up routing and initializes the node context.
For your new node, create a new file at examples/01-node.rs
in your project:
Add the following code to this file:
Here we add the #[ockam::node]
attribute to an async
main function that receives the node execution context as a parameter and returns ockam::Result
which helps make our error reporting better.
As soon as the main function starts, we use ctx.stop()
to immediately stop the node that was just started. If we don't add this line, the node will run forever.
To run the node program:
This will download various dependencies, compile and then run our code. When it runs, you'll see colorized output showing that the node starts up and then shuts down immediately 🎉.
When a worker is started on a node, it is given one or more addresses. The node maintains a mailbox for each address and whenever a message arrives for a specific address it delivers that message to the corresponding registered worker.
Workers can handle messages from other workers running on the same or a different node. In response to a message, an worker can: make local decisions, change its internal state, create more workers, or send more messages to other workers running on the same or a different node.
To create a worker, we create a struct that can optionally have some fields to store the worker's internal state. If the worker is stateless, it can be defined as a field-less unit struct.
This struct:
Must implement the ockam::Worker
trait.
Must have the #[ockam::worker]
attribute on the Worker trait implementation
Must define two associated types Context
and Message
The Context
type is usually set to ockam::Context
which is provided by the node implementation.
The Message
type must be set to the type of message the worker wishes to handle.
Add the following code to this file:
Note that we define the Message
associated type of the worker as String
, which specifies that this worker expects to handle String
messages. We then go on to define a handle_message(..)
function that will be called whenever a new message arrives for this worker.
In the Echoer's handle_message(..)
, we print any incoming message, along with the address of the Echoer
. We then take the body of the incoming message and echo it back on its return route (more about routes soon).
To make this Echoer type accessible to our main program, export it from src/lib.rs
file by adding the following to it:
When a new node starts and calls an async
main function, it turns that function into a worker with address of "app"
. This makes it easy to send and receive messages from the main function (i.e the "app"
worker).
In the code below, we start a new Echoer
worker at address "echoer"
, send this "echoer"
a message "Hello Ockam!"
and then wait to receive a String
reply back from the "echoer"
.
Create a new file at:
Add the following code to this file:
To run this new node program:
You'll see console output that shows "Hello Ockam!"
received by the "echoer"
and then an echo of it received by the "app"
.
The message flow looked like this:
Ockam run very lightweight, concurrent, and stateful actors called Ockam Workers.
Above we've , now let's create a new worker, send it a message, and receive a reply.
For a new Echoer
worker, create a new file at src/echoer.rs
in your project. We're creating this inside the src
directory so we can easily reuse the Echoer
in other examples that we'll write later in this guide:
Next, let’s explore how Ockam’s enables us to create protocols that provide end-to-end security and privacy guarantees.