RChain — распределенная вычислительная блокчейн-платформа. Как ethereum, только на порядок шустрее. В теории, ее можно масштабировать до бесконечности, первые реализации на практике позволяют обрабатывать до 40 тысяч транзакций в секунду. Технические подробности — в документе про архитектуру.
Контракты на платформе RChain написаны на языке rholang, с которым я бы хотел познакомить аудиторию хабра. Попробовать написать и выполнить программы на этом языке можно либо скачав и запустив Rchain-ноду через докер, либо воспользовавшись интерпретатором на сайте.
Полный туториал
Роланг (или просто ро) — это "процессо-ориентированный" язык. Все вычисления на нем выполняются при помощи передачи сообщений между "каналами". На каналах хранятся множества сообщений. Ро — полностью асинхронный язык, поэтому очередность, с какой сообщения поступают на каналы, не играет никакой роли. Например, с канала можно считать сообщение и затем выполнить над этим сообщением какую-нибудь операцию. Однако нельзя отправить сообщение и потом сделать что-нибудь после того, как сообщение будет получено. По крайней мере, не прописывая отдельным образом ожидание сообщения с подтверждением доставки. Стоит отметить, что в этом туториале мы будем использовать термины имя и канал как синонимы. В алгебре, на которой основан rholang, используется термин имя, но так как мы можем с их помощью посылать и получать данные, то семантически они похожи на каналы.
Контракты и отправка данных
1 contract @"HelloWorld"(return) = {
2 return!("Hello, World!")
3 } |
4 new myChannel in {
5 @"HelloWorld"!(*myChannel)
6 }
- Программа на ро — единый процесс состоящий из нескольких частей, которые исполняются синхронно. Это процессс запускается при помощи создания контракта с именем @"HelloWorld". Создание контракта запускает процесс, который вызывает копию своего "тела" каждый раз, когда он получает сообщение. Стоит отметить, что в роланге все процессы могут быть "цитированы" при помощи оператора @ и превратиться в канал. Строки — это просто особые процессы, и мы можем процитировать любой процесс и создать таким образом канал. За хранение данных на платформе RChain отвечает библиотека rspace, написанная на Scala.
- На канале
return
мы посылаем строку "Hello, World!" - Конструкция
new ... in
позволяет создать новый приватный канал. Никакой иной процесс не может отправлять или получать с этого канала сообщения. Каждое исключение необходимо указывать отдельно, отправляя имя_канала другому процессу. - Мы посылаем канал myChannel контракту по адресу @"HelloWorld". Оператор
*
означает "обратное цитирование" ("unquoting") канала и возвращает исходный процесс. В ро можно только посылать процессы по каналам, нельзя напрямую посылать каналы на каналы. Такими образом мы превращаем приватный канал в процесс перед его отправкой.
Получение данных
1 contract @"HelloAgain"(_) = {
2 new chan in {
3 chan!("Hello again, world!") |
4 for (@text <- chan) { Nil }
5 }
6 } | @"HelloAgain"!(Nil)
- Контракты обладают хотя бы одним параметром, но мы можем обойти это, указав подстановочный символ
_
. - Мы создаем новый канал
chan
. - Мы посылаем строчный процесс "Hello again, world!" по новому каналу.
- Мы слушаем новый канал и ждем единственного сообщения. Операция
for
остается заблокированной до тех пор, пока на каналеchan
не будет сообщения. В ро по каналам можно получать только имена, а вот отправлять можно и цитированные процессы. Присвоение слева от выражения<-
представляет собой паттерн имен.В данном примере это@text
, что означает, что получаемое имя является цитированным процессом и мы хотим связать этот процесс к свободной текстовой переменной. Операцияfor
ведет себя аналогично: она может читать только одно сообщение и затем становится телом, а не вызывает копию себя в каждом сообщения. В таком случае мы ничего не делаем вfor
и превращаем его в остановленный процессNil
, в принципе мы можем совершить с текстом на каналеchan
и другие операции.
Изменение состояний
1 new MakeCell in {
2 // Makes a single cell in which you can store values
3 contract MakeCell(@init, get, set) = {
4 new valueStore in {
5 valueStore!(init) |
6 contract get(ack) = {
7 for(@value <- valueStore) {
8 valueStore!(value) | ack!(value)
9 }
10 } |
11 contract set(@newValue, ack) = {
12 for(_ <- valueStore) {
13 valueStore!(newValue) | ack!(true)
14 }
15 }
16 }
17 } |
18 // Cell usage.
19 new myGet, mySet in {
20 MakeCell!(123, *myGet, *mySet) |
21 new ack in {
22 myGet!(*ack) |
23 for (@result <- ack) {
24 //result now contains the value 123
25 mySet!(456, *ack) |
26 for (_ <- ack) {
27 myGet!(*ack) |
28 for (@result <- ack) {
29 //result now contains the value 456
30 Nil
31 }
32 }
33 }
34 }
35 }
36 }
- Мы создаем новый канал
MakeCell
и затем используем его на строке 3 как имя внутреннего контракта. Никакой другой процесс, кроме кода в этом лексическом окружении может его вызвать. - Контракту
MakeCell
необходимы три аргумента. Первый аргумент — это значение, которое будет содержать эта ячейка. Второй и третий — каналы, по которым ячейка будет получать запросы на чтение и запись. Отметим, что первый аргумент должен быть процессом, а второй и третий — именами. Так как имена всегда посылают по каналам, то первым аргументом станет паттерн, начинающийся с@
, который показывает на то, что получаемое имя является цитированным процессом и мы хотим связать этот процесс с переменной. - Чтобы сохранить значение, мы создаем новый канал. Этот канал будет содержать максимум одно значение, текущее значение ячейки.
- До этой линии на канале
valueStore
нет ни одного сообщения. После того, как мы установим начальное значение, это значение станет единственным на этом канале. - Мы запускаем контракт, который слушает на канале чтения. Каждый раз, когда получено сообщение, выполняется тело конткракта.
- Мы блокируем контракт, пока ты не получим сообщение на канале
valueStore
. Так как на каналеvalueStore
может ожидать не более одного сообщения, прочитывание сообщения является своеобразным замком. - Мы снова передаем текущее значение канал
valueStore
, открывая обработку других сообщений и снимая блок. Теперь мы передаем текущее значение обратно клиенту на каналеack
. - Синхронно с get контрактом мы запускаем контракт, который слушает на set.
- Мы блокируем его, до тех пор, пока не появится сообщение на
valueStore
, а затем читаем его. Мы выбрасываем сообщение, которое прочитали. - Мы отправляем новое значение в хранилище на канале
valueStore
и даем сигнал, что операция завершилась.
- 18-36) Код, который показывает создание ячейки, установку начального значения
123
, считывание этого значения, установку значения456
, получение этого значения.
Отметим глубину слоёв, на которые распространяется вызов. Ро спроектирован специально для описания синхронных вычислений и поэтому необходимо явно указывать порядок действий там, где в других языках это само собой разумеется.
Заключение
Ро — это язык, который создан для использования на блокчейнах, но мы ещё не дошли до устройства нод, пространств имён, кошельков, Rev и флогистона, структуры сети или консенсус-алгоритма Casper.
Полный туториал.