Running Blockless Nodes
Starting Up a Minimal Cluster
For a minimal working cluster, one needs to have one head node and one worker node. For more detailed information about setting up identities and everything, refer to this Readme and the node documentation.
This section describes how to start nodes in the easiest way possible.
Head Node
From the repo root (assuming you've built the node executable over at /cmd/node
), run:
$ ./cmd/node/node --role head --rest-api ':8080'
{"level":"info","id":"12D3KooWLW6zn7YAcFCr5SqF2oxdyv9f1te4Q2droK62NpWupwpH","addresses":["/ip4/127.0.0.1/tcp/39173/p2p/12D3KooWLW6zn7YAcFCr5SqF2oxdyv9f1te4Q2droK62NpWupwpH","/ip4/172.27.145.54/tcp/39173/p2p/12D3KooWLW6zn7YAcFCr5SqF2oxdyv9f1te4Q2droK62NpWupwpH"],"boot_nodes":0,"dial_back_peers":0,"time":"2024-01-12T19:30:36+01:00","message":"created host"}
{"level":"info","port":":8080","time":"2024-01-12T19:30:36+01:00","message":"Node API starting"}
{"level":"info","role":"head","time":"2024-01-12T19:30:36+01:00","message":"Blockless Node starting"}
{"level":"info","component":"node","topics":["blockless/b7s/general"],"time":"2024-01-12T19:30:36+01:00","message":"topics node will subscribe to"}
{"level":"info","component":"node","concurrency":10,"time":"2024-01-12T19:30:36+01:00","message":"starting node main loop"}
Some things to take note of:
- REST API is served at port 8080 on all interfaces
- private key is not specified so node will generate a random identity (peer ID)
- listens on all interfaces
- port is not specified, so node will pick a random port
If you checkout the first log message, it will have the connection and identity info for this node:
{
"level": "info",
"id": "12D3KooWLW6zn7YAcFCr5SqF2oxdyv9f1te4Q2droK62NpWupwpH",
"addresses": [
"/ip4/127.0.0.1/tcp/39173/p2p/12D3KooWLW6zn7YAcFCr5SqF2oxdyv9f1te4Q2droK62NpWupwpH",
"/ip4/172.27.145.54/tcp/39173/p2p/12D3KooWLW6zn7YAcFCr5SqF2oxdyv9f1te4Q2droK62NpWupwpH"
],
"boot_nodes": 0,
"dial_back_peers": 0,
"time": "2024-01-12T19:30:36+01:00",
"message": "created host"
}
The node peer ID is 12D3KooWLW6zn7YAcFCr5SqF2oxdyv9f1te4Q2droK62NpWupwpH
while /ip4/127.0.0.1/tcp/39173/p2p/12D3KooWLW6zn7YAcFCr5SqF2oxdyv9f1te4Q2droK62NpWupwpH
is the multiaddress we use to connect to the node.
Worker Node
Prerequisite - install Blockless Runtime (opens in a new tab).
From the repo root, run:
$ ./cmd/node/node --peer-db worker-peer-db --function-db worker-function-db --workspace worker-workspace --runtime-path ~/.local/blockless-runtime/bin --runtime-cli bls-runtime --boot-nodes /ip4/127.0.0.1/tcp/39173/p2p/12D3KooWLW6zn7YAcFCr5SqF2oxdyv9f1te4Q2droK62NpWupwpH
{"level":"info","id":"12D3KooWS7hQiSpq8FPeCpp6PeAkYEHpuw8opaLGAS2zzWhWgGei","addresses":["/ip4/127.0.0.1/tcp/37383/p2p/12D3KooWS7hQiSpq8FPeCpp6PeAkYEHpuw8opaLGAS2zzWhWgGei","/ip4/172.27.145.54/tcp/37383/p2p/12D3KooWS7hQiSpq8FPeCpp6PeAkYEHpuw8opaLGAS2zzWhWgGei"],"boot_nodes":1,"dial_back_peers":0,"time":"2024-01-12T19:33:36+01:00","message":"created host"}
{"level":"info","role":"worker","time":"2024-01-12T19:33:37+01:00","message":"Blockless Node starting"}
{"level":"info","component":"node","topics":["blockless/b7s/general"],"time":"2024-01-12T19:33:37+01:00","message":"topics node will subscribe to"}
{"level":"info","component":"node","concurrency":10,"time":"2024-01-12T19:33:37+01:00","message":"starting node main loop"}
Notes:
peer-db
,function-db
andworkspace
are not mandatory arguments. However, since we have started the head node with the default flag values, we're changing them here so as to not cause collisions. If you don't run the risk of such collisions, you can omit theseruntime-path
andruntime-cli
specify the path and name of the runtime that the worker will try to execute - the Blockless Runtimeboot-nodes
specifies the addresses of nodes that this node will try to connect to. In this case, we have specified the multiaddress of the head node we started in previous step
You can take note of the peer ID of our Worker node (12D3KooWS7hQiSpq8FPeCpp6PeAkYEHpuw8opaLGAS2zzWhWgGei
) - that will come in handy later.
Issue an Execution Request
Using cURL, you can issue an HTTP request to the head node to execute a simple Hello World request:
curl --location 'localhost:8080/api/v1/functions/execute' \
--header 'Content-Type: application/json' \
--data '{
"function_id": "bafybeia24v4czavtpjv2co3j54o4a5ztduqcpyyinerjgncx7s2s22s7ea",
"method": "hello-world.wasm",
"config": {
"number_of_nodes": 1
}
}' | jq
You should then see a response that looks like:
{
"code": "200",
"request_id": "50f1228a-87d3-4b40-a950-592f388317f8",
"results": [
{
"result": {
"stdout": "Hello, world!\n",
"stderr": "",
"exit_code": 0
},
"peers": [
"12D3KooWS7hQiSpq8FPeCpp6PeAkYEHpuw8opaLGAS2zzWhWgGei"
],
"frequency": 100
}
],
"cluster": {
"peers": [
"12D3KooWS7hQiSpq8FPeCpp6PeAkYEHpuw8opaLGAS2zzWhWgGei"
]
}
}
This minimal HTTP request says the following things:
- execute function with the ID (CID) of
bafybeia24v4czavtpjv2co3j54o4a5ztduqcpyyinerjgncx7s2s22s7ea
- execute it on one node
We issue this HTTP request to the head node REST API by sending it to localhost:8080
. Head node takes a little time, then replies back. The request_id
field is a UUID generated to uniquely identify this execution request. The results
field contains a list of distinct execution results, ordered by frequency in which they appear. Since here we had a single node, its execution result is the only one. Note that the ID of the peer that returned this result is the ID of our Worker node! In cases where we have multiple nodes executing requests, the cluster
field will list all peers that participated in this execution.
If you check out the logs of the Head and Worker node, you can find a lot of interesting information about what went on with this simple request!
Starting a Cluster with Subgroups
Start Worker Nodes
This example builds on the previous one. Here, we'll run nodes that are part of different subgroups.
Now, let's start two more workers using a similar command line as before:
$ # worker 1
$ ./cmd/node/node --topic blockless-subgroup-1 --peer-db worker1-peer-db --function-db worker1-function-db --workspace worker1-workspace --runtime-path ~/.local/blockless-runtime/bin --runtime-cli bls-runtime --boot-nodes /ip4/127.0.0.1/tcp/39173/p2p/12D3KooWLW6zn7YAcFCr5SqF2oxdyv9f1te4Q2droK62NpWupwpH
$ # worker 2
$ ./cmd/node/node --topic blockless-subgroup-2 --peer-db worker2-peer-db --function-db worker1-function-db --workspace worker2-workspace --runtime-path ~/.local/blockless-runtime/bin --runtime-cli bls-runtime --boot-nodes /ip4/127.0.0.1/tcp/39173/p2p/12D3KooWLW6zn7YAcFCr5SqF2oxdyv9f1te4Q2droK62NpWupwpH
Note that both nodes connect to the same head node.
One difference is in the --topic
flag - one node makes itself part of the blockless-subgroup-1
, while another one specifies blockless-subgroup-2
.
If we check the node log output, it tells us to which topics it subscribes, or - which subgroups it's part of:
{
"level": "info",
"component": "node",
"topics": [
"blockless-subgroup-1",
"blockless/b7s/general"
],
"time": "2024-01-12T20:04:09+01:00",
"message": "topics node will subscribe to"
}
The blockless/b7s/general
topic is the top-level group that all nodes are part of.
Issue an Execution Request
Let's issue the same request as before, but now let's say that we want TWO nodes to execute it:
curl --location 'localhost:8080/api/v1/functions/execute' \
--header 'Content-Type: application/json' \
--data '{
"function_id": "bafybeia24v4czavtpjv2co3j54o4a5ztduqcpyyinerjgncx7s2s22s7ea",
"method": "hello-world.wasm",
"config": {
"number_of_nodes": 2
}
}' | jq
The response should look like:
{
"code": "200",
"request_id": "a2ee6c52-f895-4c51-95c2-e18194fa1232",
"results": [
{
"result": {
"stdout": "Hello, world!\n",
"stderr": "",
"exit_code": 0
},
"peers": [
"12D3KooWNTcry5sCgP9DBykNmArMAu35mFdSmCfTjkTkWyRFyipa",
"12D3KooWCKG1dKD6muZyz8mLJasptd4ACmrZjLHG9yXmxaU1wd9t"
],
"frequency": 100
}
],
"cluster": {
"peers": [
"12D3KooWNTcry5sCgP9DBykNmArMAu35mFdSmCfTjkTkWyRFyipa",
"12D3KooWCKG1dKD6muZyz8mLJasptd4ACmrZjLHG9yXmxaU1wd9t"
]
}
}
Issue an Execution Request to a Subgroup
Lets now tweak the execution request by specifying a subgroup. This can be done by specifying the subgroup
field in the JSON request. Note that we've again specify we only want ONE node - this is because the subgroup only has a single node. If we'd request two nodes - our request would fail as there wouldn't be enough nodes to fulfil our requirement.
{
"subgroup": "blockless-subgroup-1",
"function_id": "bafybeia24v4czavtpjv2co3j54o4a5ztduqcpyyinerjgncx7s2s22s7ea",
"method": "hello-world.wasm",
"config": {
"number_of_nodes": 1
}
}
Head node response:
{
"code": "200",
"request_id": "5ba2c78d-6773-41a5-9eb8-1e8eef93d3f0",
"results": [
{
"result": {
"stdout": "Hello, world!\n",
"stderr": "",
"exit_code": 0
},
"peers": [
"12D3KooWNTcry5sCgP9DBykNmArMAu35mFdSmCfTjkTkWyRFyipa"
],
"frequency": 100
}
],
"cluster": {
"peers": [
"12D3KooWNTcry5sCgP9DBykNmArMAu35mFdSmCfTjkTkWyRFyipa"
]
}
}
From the response it can be seen that the node that executed this work has the ID of 12D3KooWNTcry5sCgP9DBykNmArMAu35mFdSmCfTjkTkWyRFyipa
. This is because only that node makes itself available to work assigned to this subgroup.
If we repeat this query but change the subgroup
field to blockless-subgroup-2
the work would be tasked to the other node, in our case 12D3KooWCKG1dKD6muZyz8mLJasptd4ACmrZjLHG9yXmxaU1wd9t
.