Примеры юботов


Написание и развертывание вашего первого юбота.

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