D2.2 -- MVP Alpha (UPSYCLE)

We are pleased to announce an initial reference software implementation of UPSYCLE Router.

Introduction

As part of DREAM we are researching and developing decentralized communication protocols that facilitate peer-to-peer group collaboration with strong privacy and scalability guarantees. The component of DREAM which implements this is called UPSYCLE.

An integral part of the UPSYCLE architecture, upsycle-router is the message router and service component which is run on all devices in the system — a phone, a laptop, a core node in a data center — which handles authentication, encryption, and translation of cryptographic public keys into routing information. Services connect to the nearest message router, and message routers connect to each other. In this way the message routers form the backbone of the system and all parts of the system can communicate with each other. Once connected, services can publish updates to a topic (a multicast group which is identified by its public key), or directly send a message to another service or router (whose address is also a public key), and the message router makes the routing, sending, and receiving transparent.

The message router also contains a cache of recently seen messages, and a temporary queue for messages which should be retransmitted later ; it maintains a subscription registry of which nodes in the system are subscribed to which topics. Applications using UPSYCLE pub/sub should implement end-to-end encryption such that an intermediate message router can only read the outermost layer of a message to know how to handle it, but not does not have access to the payload.

As part of UPSYCLE there is also ongoing research and development into peer-to-peer protocols including peer discovery, clustering, and dissemination, which form another major part of the system. Once these are ready they can be connected to this implementation, for example by creating a service which understands these protocols and communicates through its local message router.

See the technical specification for upsycle-router.

Implementation

The repository is hosted at our GitLab and the API docs are available here.

This release is tagged 0.1.0-alpha1.

We provide library and application code in OCaml for running one or more message router(s) and one or more local services associated to each message router. The library code does all the work of setting up the services and message routers and connecting them to each other. The various message types have all been implemented and we provide high-level functions for encapsulating messages in unicast/multicast wrappers and encoding/decoding them to/from the binary encoding format CBOR, as well as high-level functions for sending the various kinds of messages with arbitrary payloads.

The application code sets up a console-based front-end to launch the code, allow various customizations using the associated YAML files, and query and manipulate the state. One can also optionally send commands using the keyboard or a control interface, which is useful for scripting, testing and demo purposes. The applications are loosely coupled to the library code and interested developers are encouraged to write their own.

We also provide an example scenario using three message routers and four services, which you are encouraged to experiment with.

The general architecture is based on the Lwt (”light-weight threads”) library. The word “thread” is used a bit loosely here — they are better thought of as cooperative asynchronous promises which greatly reduce the challenges of classical threading, like deadlocks and race conditions. A server thread running a streaming parser places incoming messages on an internal queue, which are then handled by a handler thread, and the promises can be manipulated by functional programming techniques (the data structure in which promises are stored form a so-called monad). This design allows very high throughput and a system with very high up-time that is easy to reason about, also in the case of errors.

Try it out!

See the README for instructions for configuring and running the software and trying the examples, and also for a more in-depth discussion of various topics.

What’s next

The code has been designed with the goal of eventually running in a container, for example a MirageOS unikernel. This is done by clearly separating I/O from pure code and by using module functors to abstract away the Unix dependency where possible. However, at the moment it is still dependent on Unix in a few places, in particular the streaming Angstrom parser, which uses angstrom-lwt-unix, and the TLS code, which uses conduit-lwt-unix. Conduit is part of the MirageOS ecosystem and we are using the pure OCaml TLS stack, so the Unix-specific parts are expected to be easy to abstract away. (The Mirage version of Conduit provides abstractions for input and output which are not necessarily tied to Unix file descriptors.) The rest will require some effort in a future stage, and the various C libraries which are bound to by the various OCaml dependencies also need to be audited for compatibility.

Discussion

You’re welcome to discuss the software with the DREAM Catchers!

NGI POINTER

This is experimental software made for P2Pcollab within the DREAM project.

This project has received funding from the European Union’s Horizon 2020 research and innovation programme within the framework of the NGI-POINTER Project funded under grant agreement No 871528.

European Commission

Well, well, well…

So I embarked into setting up the following:

Hostname OS Ocaml version Status
upsycle-mr-a.dream.public.cat NixOS 21.05 4.12.0 :negative_squared_cross_mark: boots fine now…
upsycle-mr-b.dream.public.cat Alpine 3.14 4.12.0r0 :negative_squared_cross_mark: builds fine! But breaks at runtime
upsycle-mr-c.dream.public.cat Debian 11 4.11.1 :white_check_mark: works fine!

As you can see, it’s a we’re doing disaster recovery. I wonder how you manage to make a solid work with OCaml…

NixOS on Hetzner Cloud

Really puzzles me. This is the one that should be straightforward, yet something goes wrong and I have no clue (yet). Once installed, I expect the build (from nix.flakes) to run smoothly.

I figured a way to build a NixOS VM on the Hetzner Cloud and could install ocaml-seaboar with the nix flakes, but then ocaml-conduit-dream is not building correctly yet: I’m missing dependencies, opam install . complains about missing make m4 cc and nix-env -iA nixos.make does not work. Meh. I’ll have a look tomorrow.

Alpine Linux

Apparently MUSL is not playing nice with OCaml. I updated the README with dependencies to build on Alpine Linux 3.14.

upsycle-mr-b:~/dream-upsycle-mr$ ./bin/run-msg-router b
٭ set-conduit-backend ()
٭ No conduit backend chosen, defaulting to ocaml-tls
[ env ] CONDUIT_TLS native
٭ [ chdir ] /home/dream/dream-upsycle-mr/bin/..
٭ go (b)
٭ dune exec --display quiet -- upsycle-router/bin/message_router.exe --config /home/dream/dream-upsycle-mr/bin/../conf/config-message-router-b.yaml
    ocamlopt upsycle-router/bin/message_router.exe (exit 2)

[...]

/usr/lib/gcc/x86_64-alpine-linux-musl/10.3.1/../../../../x86_64-alpine-linux-musl/bin/ld: /home/dream/.opam/ocaml-system/lib/stdint/libstdint_stubs.a(uint48_conv.o): in function `uint48_of_uint128':
/home/dream/.opam/ocaml-system/.opam-switch/build/stdint.0.7.0/_build/default/lib/uint48_conv.c:172: undefined reference to `get_uint128'
/usr/lib/gcc/x86_64-alpine-linux-musl/10.3.1/../../../../x86_64-alpine-linux-musl/bin/ld: /home/dream/.opam/ocaml-system/lib/stdint/libstdint_stubs.a(uint56_conv.o): in function `uint56_of_int128':
/home/dream/.opam/ocaml-system/.opam-switch/build/stdint.0.7.0/_build/default/lib/uint8_conv.c:172: undefined reference to `get_uint128'
collect2: error: ld returned 1 exit status
File "caml_startup", line 1:
Error: Error during linking (exit code 1)

Debian 11

Since this is the most stable, I’m trying to get this running first. I still hope that ocaml v4.11.0 remains the minimum target, and did not move upwards.

https://gitlab.com/public.dream/upsycle/message-router/-/issues/3

previously broken details

I’m adding system packages as errors go. ocaml-seaboar builds nicely. ocaml-conduit-dream currently breaks:

dream@upsycle-mr-c:~/ocaml-conduit-dream$ opam install .
[conduit-async-dream.4.0.1-6] no changes from git+file:///home/dream/ocaml-conduit-dream#master
[conduit-dream.4.0.1-6] no changes from git+file:///home/dream/ocaml-conduit-dream#master
[conduit-lwt-dream.4.0.1-6] no changes from git+file:///home/dream/ocaml-conduit-dream#master
[conduit-lwt-unix-dream.4.0.1-6] no changes from git+file:///home/dream/ocaml-conduit-dream#master
[conduit-mirage-dream.4.0.1-6] no changes from git+file:///home/dream/ocaml-conduit-dream#master
[NOTE] Package conduit-async-dream is already installed (current version is 4.0.1-6).
[NOTE] Package conduit-dream is already installed (current version is 4.0.1-6).
[NOTE] Package conduit-lwt-dream is already installed (current version is 4.0.1-6).
[NOTE] Package conduit-mirage-dream is already installed (current version is 4.0.1-6).
The following actions will be performed:
  ∗ install conduit-lwt-unix-dream 4.0.1-6*
Do you want to continue? [Y/n] y

<><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><><><>
[ERROR] The compilation of conduit-lwt-unix-dream failed at "/home/dream/.opam/opam-init/hooks/sandbox.sh build dune build -p conduit-lwt-unix-dream -j 1".

#=== ERROR while compiling conduit-lwt-unix-dream.4.0.1-6 =====================#
# context     2.0.8 | linux/x86_64 | ocaml-system.4.11.1 | pinned(git+file:///home/dream/ocaml-conduit-dream#master#cc40f1a9)
# path        ~/.opam/default/.opam-switch/build/conduit-lwt-unix-dream.4.0.1-6
# command     ~/.opam/opam-init/hooks/sandbox.sh build dune build -p conduit-lwt-unix-dream -j 1
# exit-code   1
# env-file    ~/.opam/log/conduit-lwt-unix-dream-59981-90b366.env
# output-file ~/.opam/log/conduit-lwt-unix-dream-59981-90b366.out
### output ###
# 296 |   >>= fun (fd, ic, oc, info) ->
# [...]
# Error: This pattern matches values of type 'a * 'b * 'c * 'd
#        but a pattern was expected which matches values of type
#          Lwt_unix.file_descr * Lwt_io.input_channel * Lwt_io.output_channel
#     ocamlopt src/conduit-lwt-unix/.conduit_lwt_unix.objs/native/conduit_lwt_unix.{cmx,o} (exit 2)
# (cd _build/default && /usr/bin/ocamlopt.opt -w -40 -g -I src/conduit-lwt-unix/.conduit_lwt_unix.objs/byte -I src/conduit-lwt-unix/.conduit_lwt_unix.objs/native -I /home/dream/.opam/default/lib/angstrom -I /home/dream/.opam/default/lib/asn1-combinators -I /home/dream/.opa
m/default/lib/astring -I /home/dream/.opam/default/lib/base/caml -I /home/dream/.opam/default/lib/base64 -I /home/dream/.opa[...]
# File "src/conduit-lwt-unix/conduit_lwt_unix.ml", line 296, characters 10-28:
# 296 |   >>= fun (fd, ic, oc, info) ->
#                 ^^^^^^^^^^^^^^^^^^
# Error: This pattern matches values of type 'a * 'b * 'c * 'd
#        but a pattern was expected which matches values of type
#          Lwt_unix.file_descr * Lwt_io.input_channel * Lwt_io.output_channel



<><> Error report <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
┌─ The following actions failed
│ λ build conduit-lwt-unix-dream 4.0.1-6
└─
╶─ No changes have been performed

I’ll have a look after lunch

Could you try with the latest tag?

git pull
git reset --hard dream-v4.0.1-8

@tg-x was working on this but I don’t know if it’s stable yet.

1 Like

Debian 11 will work for sure. I just created a fresh opam switch for 4.11.0 and everything builds and runs. (But there were indeed 2 untagged commits in ocaml-conduit-dream, which explains the message above).

I’ll change the minimum version to 4.11.0 in the message router opam and add lower bounds on all the dependencies just in case.

Latest tag builds fine, thank you!

But message-router does not build. I see that ocaml minimum version is at 4.12.0, that must be the issue. Testing with 4.11… Seems to be working…

dream@upsycle-mr-c:~/message-router$ opam install --deps-only --with-test .
The following actions will be performed:
  ∗ install conf-libffi       2.0.0
  ∗ install integers          0.5.1
  ∗ install conf-which        1
  ∗ install ppx_deriving      5.2.1
  ∗ install lwt_ppx           2.0.2
  ∗ install angstrom-lwt-unix 0.15.0
  ∗ install ctypes-foreign    0.18.0
  ∗ install ctypes            0.19.1
  ∗ install yaml              3.0.0
  ∗ install hacl-star-raw     0.4.1
  ∗ install ppx_deriving_yaml 0.1.0
  ∗ install hacl-star         0.4.1
===== ∗ 12 =====

OK, I update the previous post to match the working Debian 11 install with ocaml 4.11.1 and open an issue: Ocaml dependency unnecessarily high (#3) · Issues · DREAM / UPSYCLE / upsycle-router · GitLab

1 Like

@misterfish functions.bash -> ../libs/alleycat-bash/functions.bash is missing from the message-router repo!

dream@upsycle-mr-c:~/message-router$ git blame libs/alleycat-bash/functions.bash
fatal: no such path 'libs/alleycat-bash/functions.bash' in HEAD

There’s also:

dream@upsycle-mr-c:~/message-router$ git blame bin/alleycat/functions.bash
897ed4f4 (Alleycat 2021-09-08 14:19:43 +0200 1) ../functions.bash

no functions.bash in bin/

I do have the files locally as of commit 969b8de0253e41ecbec948ce6d12f19adc1d3846.

git submodule update --init --recursive

I’ve added it to the readme.

1 Like

@all I’ve updated the draft page for the deliverable (above). Feedback is welcome. Unfortunately there is not much time for feedback as we are delivering in two days!

Feedback is also welcome on the README.md in the upsycle-router repo.

nix build should work now

EDIT: the last commit didnt’t get pushed yesterday, should be fixed now

@all Please note that the repository has been moved to

git@gitlab.com:public.dream/upsycle/upsycle-router
https://gitlab.com/public.dream/upsycle/upsycle-router.git

and the master branch has been renamed to main.

1 Like

We moved the release date to Friday so that we can wrap up loose ends with the builds and readme.

Hi, we have just published version 0.1.0-alpha2.

Changes with respect to 0.1.0-alpha1 are:

  • Breaking: the configuration fields in the message router yaml configurations have been simplified. Please see the new examples.
  • Breaking: relative paths in the configuration files are now resolved relative to the location of the config file instead of the project root.
  • Internal: some reorganization and improvement of types and parsing of configuration files.
  • General improvements to output, pretty printing, help messages, and the examples.
  • Most configuration fields which expect a private key can now take a base-64 string or the path to a key file in .pem format, and those which expect a public key can take a base-64 string, or the path to a key file or a cert file in .pem format.
  • The ‘p’ and ‘P’ commands for upsycle-router have been re-implemented and improved.
  • The unicast and multicast routing algorithms have now been completely implemented according to the spec.
  • Changed the ‘n’ and ‘N’ commands for upsycle-router to mock-receive messages for the known local services instead of for random ones (or else they would get dropped by the unicast algorithm.)
1 Like

These sound like good changes.

Can you detail the change? What does it entail for the documentation and possibilities of the software?

Since you made breaking changes, why not up the minor version? I know SemVer goes “do whatever you want” with 0.*, but it sounds more logical (at least to me). I will try upgrading…

1 Like

Very good, congratulations!

I imagine the next step for UPSYCLE and the message router is connecting DMC and UPSYCLE, and for the message router to be able to send and share data from a DMC Repository, over the network.

Do you see that as technically doable, regarding the current structure of the message router?

1 Like

It won’t really affect the functionality – the changes are at a detailed level so you probably won’t notice at this stage. What we’ve done is implement the rules in the algorithms as closely to the letter as possible. For example, if a unicast message arrives which is neither FROM a local service nor FOR a local service, then it gets dropped. In -alpha1 it didn’t get dropped. So it’s just making the routing rules stricter and more precise.

Good point, it was just to avoid confusion with the alpha tags. 0.1.1-alpha1 would be confusing and 0.1.1-alpha2 seems a bit weird, but maybe it’s better. You suggest 0.1.1-alpha2?

1 Like

I just pulled from the repo and only see 0.1.0-alpha1 tag.

Given your arguments, I would suggest 0.1.1-alpha1, keeping alpha1 so that all repositories can use that tag: changing one would require updating the others for good measure, so it’s better to keep them all synchronized on the alpha methinks.

Thanks!

Yes it’s technically doable as far as we can see. It would also require some changes to the DMC code probably.