Page: Примеры юботов
2021-05-06 21:05
Примеры юботов
Написание и развертывание вашего первого юбота.
git clone https://redmine.sergeych.net/ubot-demo.git
cd ubot-demo
sbt
# дождаться инициализации sbt консоли
run
Код демонстрации позволяет готовить юбот и выполнять запросы к нему. Он также содержит серию примеров скриптов .js, показывающих основы юботов.
Hello world
Наш первый пример юбота, который возвращает строку "hello world!" в качестве результата. Для запуска такого юбота нам понадобится следующее:
JS-скрипт
Скрипт определяет функции, которые позже могут быть объявлены как «облачные методы» в исполняемом контракте юбота.
async function helloWorld() {
return "hello world!"
}
Исполняемый контракт
Исполняемый контракт - это обычный контракт Universa, имеющий особую структуру.
state:
data:
js: <КОД JS-СКРИПТА>
cloud_methods:
helloWorld:
quorum:
size: 7
pool:
size: 8
Этот контракт определяет js-скрипт и единственный облачный метод helloWorld
, который будет выполняться в пуле из 8 исполнителей с кворумом 7.
Контракт-заявка
Чтобы действительно запустить определенный облачный метод, нам нужно сформировать контракт-заявку, который также является обычным контрактом Universa, имеющим особую структуру.
state:
data:
executable_contract_id: <ID ИСПОЛНЯЕМОГО КОНТРАКТА>
method_name: helloWorld
method_args: []
definition:
references:
- __type: Reference
name: executable_contract_constraint
where:
all_of:
- ref.id==this.state.data.executable_contract_id
В state.data
содержится информация, необходимая для запуска облачного метода:
- идентификатор исполняемого контракта
- имя облачного метода
- аргументы облачного метода
Контракт также должен содержать ограничение названное executable_contract_constraint
с условием поиска исполняемого контракта по его идентификатору.
Выполнение метода
Выполнение метода состоит из двух этапов: запрос обычной сети Universa для получения сессии (предоставляется контракт-заявка) и запрос исполнения юбота (предоставляется контракт-заявка и сессия).
Запрос для получения сессии
Сессия инициируется вызовом команды ubotCreateSession
на случайной ноде, с предоставлением транзакшн-пака с контрактом-заявкой в параметре packedRequest
.
val client = new Client("$topologyname",null,clientKey)
client.command("ubotCreateSession", "packedRequest", requestContract.getPackedTransaction)
Чтобы убедиться, что сессия готова, клиент должен запросить все ноды для определения его статуса.
val response = client.getClient(i).command("ubotGetSession", "requestId", requestContract.getId)
if(res.getBinderOrThrow("session").getString("state",null) == "OPERATIONAL") {
// session ready on a node
}
Результат работы инструмента будет таким:
Node 1 Node 2 Node 3 Node 4
OPERATIONAL OPERATIONAL OPERATIONAL OPERATIONAL
Node 5 Node 6 Node 7 Node 8
OPERATIONAL OPERATIONAL OPERATIONAL OPERATIONAL
Node 9 Node 10
OPERATIONAL OPERATIONAL
Как только сессия будет готова, клиент может запросить пул сессии:
val pool = res.getBinderOrThrow("session").getArray("sessionPool")
Пул - это массив номеров юбот-серверов, которым назначен запуск экземпляров юбота.
Запрос исполнения юбота
Подключение к случайному юбот-серверу пула:
val n = Do.sample(pool.asJava)
val ubotConnection = client.connectToUbot(n)
Запрос на исполнение облачного метода:
ubotConnection.command("executeCloudMethod", "contract", requestContract.getPackedTransaction)
Запрос состояния выполнения юбота:
val response = ubotConnection.command("getState", "requestContractId", requestContract.getId, "getQuanta", true)
Результат работы инструмента будет таким:
UBot 1 UBot 3 UBot 9 UBot 28
FINISHED FINISHED FINISHED FINISHED
hello world! hello world! hello world! hello world!
{} {} {} {}
[] [] [] []
53.30 53.26 53.28 53.30
UBot 12 UBot 13 UBot 14 UBot 15
FINISHED FINISHED FINISHED FINISHED
hello world! hello world! hello world! hello world!
{} {} {} {}
[] [] [] []
53.28 53.43 53.40 53.37
Для каждого юбот-сервера пула инструмент выводит:
UBot 1 <<==== Номер юбот-сервера
FINISHED <<==== Состояние выполнения экземпляра юбота
hello world! <<==== Результат экземпляра юбота
{} <<==== Содержимое хранилища пула
[] <<==== Содержимое хранилища исполнителей (кортежи)
53.30 <<==== Количество использованных квантов U
Чтение/запись в хранилище пула
Скрипт
async function write() {
await writeSingleStorage({foo:1, bar:2})
return "ok"
}
async function read() {
let x = await getSingleStorage();
return x.foo
}
Исполняемый контракт (чтение/запись)
cloud_methods:
read:
quorum:
size: 7
pool:
size: 8
write:
quorum:
size: 4
pool:
size: 4
launcher: owner_link
Метод write
объявлен с определением свойства launcher
. Это свойство содержит имя роли, которую нужно разрешить для запуска облачного метода.
Контракт-заявка (запись)
- __type: Reference
name: executable_contract_constraint
where:
all_of:
- ref.id==this.state.data.executable_contract_id
- this can_perform ref.state.roles.owner_link
В ограничении executable_contract_constraint
для таких методов должно быть указано дополнительное условие:
"this can_perform ref.state.roles.$LAUNCHER_ROLE_NAME"
Выполнение метода "write" (пул из 4)
UBot 20 UBot 8 UBot 11 UBot 30
FINISHED FINISHED FINISHED FINISHED
ok ok ok ok
{bar=2, foo=1} {bar=2, foo=1} {bar=2, foo=1} {bar=2, foo=1}
[] [] [] []
73.32 73.28 73.28 73.32
Выполнение метода "read" (пул из 8)
UBot 19 UBot 4 UBot 5 UBot 6
FINISHED FINISHED FINISHED FINISHED
1 1 1 1
{bar=2, foo=1} {bar=2, foo=1} {bar=2, foo=1} {bar=2, foo=1}
[] [] [] []
57.31 57.26 57.27 54.16
UBot 22 UBot 7 UBot 11 UBot 30
FINISHED FINISHED FINISHED FINISHED
1 1 1 1
{bar=2, foo=1} {bar=2, foo=1} {bar=2, foo=1} {bar=2, foo=1}
[] [] [] []
54.22 54.16 54 54.22
Чтение/запись в хранилище исполнителей (кортеж)
Скрипт
async function writeTuple() {
// Получение уникальных номеров для экземпляра юбота
let num = await getUBotNumber(); // возвращает уникальный номер юбот-сервера
let inPool = await getUBotNumberInPool(); // возвращает порядковый номер экземпляра юбота в пуле
// Каждый юбот записывает своё собственное значение
await writeMultiStorage({x: num*num+inPool})
return "ok"
}
async function readTuple() {
let records = await getMultiStorage();
var result = 0;
for (let r of records)
result += r.x;
return result
}
Выполнение метода "writeTuple" (пул из 4)
UBot 17 UBot 35 UBot 7 UBot 29
FINISHED FINISHED FINISHED FINISHED
ok ok ok ok
{} {} {} {}
[{result={x=52}, ubo[{result={x=52}, ubo[{result={x=52}, ubo[{result={x=52}, ubo
90.47 90.47 90.66 90.66
Хранилище исполнителей (кортеж) в конце выполнения:
[{result={x=52}, ubot_number=7}, {result={x=291}, ubot_number=17}, {result={x=842}, ubot_number=29}, {result={x=1225}, ubot_number=35}]
Выполнение метода "readTuple" (пул из 8)
UBot 1 UBot 17 UBot 19 UBot 35
FINISHED FINISHED FINISHED FINISHED
2410 2410 2410 2410
{} {} {} {}
[{result={x=52}, ubo[{result={x=52}, ubo[{result={x=52}, ubo[{result={x=52}, ubo
54.16 54 54.17 54
UBot 24 UBot 8 UBot 28 UBot 14
FINISHED FINISHED FINISHED FINISHED
2410 2410 2410 2410
{} {} {} {}
[{result={x=52}, ubo[{result={x=52}, ubo[{result={x=52}, ubo[{result={x=52}, ubo
54 54.18 54.17 54
Регистрация контракта
Скрипт
const ItemState = require("itemstate").ItemState;
const roles = require('roles');
const crypto = require('crypto');
async function registerRoot() {
try {
// Создается контракт пула по умолчанию с ролями creator, issuer and owner, установленными как КВОРУМ от текущего пула.
// Поля created_at и другие (например, соль), которые могут отличаться, синхронизируются между исполнителями.
let contract = await createPoolContract();
await contract.seal(true);
let packed = await contract.getPackedTransaction();
// Регистрация транзакции с контрактом пула
let ir = await registerContract(packed);
if (ir.state !== ItemState.APPROVED.val)
return {error: "Contract state: " + ir.state};
await writeSingleStorage({packed:packed});
return contract.id.base64;
} catch (err) {
return err.message
}
}
async function registerRevision(address) {
try {
let storage = await getSingleStorage();
let contract = await Contract.fromPackedTransaction(storage.packed);
contract = await contract.createRevision();
contract.registerRole(new roles.SimpleRole("owner", new crypto.KeyAddress(address)));
await contract.seal(true);
// Синхронизирует поля created_at и другие (например, соль), которые могут отличаться у разных исполнителей,
// и устанавливает роль creator, как КВОРУМ от текущего пула.
let packed = await preparePoolRevision(await contract.getPackedTransaction())
let ir = await registerContract(packed);
if (ir.state !== ItemState.APPROVED.val)
return {error: "Revision state: " + ir.state};
await writeSingleStorage({packed:packed});
return contract.id.base64;
} catch (err) {
return err.message
}
}
Выполнение метода "registerRoot"
UBot 5 UBot 7 UBot 25 UBot 26
FINISHED FINISHED FINISHED FINISHED
qrt4/aJolJOUM6NTFbQRqrt4/aJolJOUM6NTFbQRqrt4/aJolJOUM6NTFbQRqrt4/aJolJOUM6NTFbQR
{packed=3__type{Tra{packed=3__type{Tra{packed=3__type{Tra{packed=3__type{Tra
[] [] [] []
91.37 91.49 91.34 91.43
UBot 10 UBot 27 UBot 28 UBot 13
FINISHED FINISHED FINISHED FINISHED
qrt4/aJolJOUM6NTFbQRqrt4/aJolJOUM6NTFbQRqrt4/aJolJOUM6NTFbQRqrt4/aJolJOUM6NTFbQR
{packed=3__type{Tra{packed=3__type{Tra{packed=3__type{Tra{packed=3__type{Tra
[] [] [] []
91.40 91.48 91.49 91.49
Выполнение метода "registerRevision"
UBot 18 UBot 2 UBot 21 UBot 5
FINISHED FINISHED FINISHED FINISHED
n0JBe+GnPnSzQSEeyps1n0JBe+GnPnSzQSEeyps1n0JBe+GnPnSzQSEeyps1n0JBe+GnPnSzQSEeyps1
{packed=3__type{Tra{packed=3__type{Tra{packed=3__type{Tra{packed=3__type{Tra
[] [] [] []
115.65 115.50 115.55 115.48
UBot 24 UBot 29 UBot 30 UBot 15
FINISHED FINISHED FINISHED FINISHED
n0JBe+GnPnSzQSEeyps1n0JBe+GnPnSzQSEeyps1n0JBe+GnPnSzQSEeyps1n0JBe+GnPnSzQSEeyps1
{packed=3__type{Tra{packed=3__type{Tra{packed=3__type{Tra{packed=3__type{Tra
[] [] [] []
115.55 115.45 115.40 115.71