Demo app

git clone https://redmine.sergeych.net/ubot-demo.git
cd ubot-demo
sbt
# wait for sbt console to initialize
run

The code of the demo allows you to prepare and execute ubot requests. It also contains serie of sample .js scripts showing the basics of Ubots.

Hello world

Our first example Ubot that returns "hello world!" string as the result. In order to run such a ubot we need the following:

JS-script

The script defines functions that can later be declated as "cloud methods" within so called executable contract

async function helloWorld() {
    return "hello world!"
}

Executable contract

Executable contract is regular Universa contract having special structure.

state:
  data:
    js: <CONTENTS OF JS SCRIPT FILE>
    cloud_methods:
      helloWorld:
        quorum:
          size: 7
        pool:
          size: 8

This contract defines js-script and a single cloud method named helloWorld to be executed on a pool of 8 workers with qourum of 7.

Execution request contract

To actually run defined method we need to form so called request contract that is also regular Universa contract having special structure.

state:
  data:
    executable_contract_id: <ID OF EXECUTABLE CONTRACT>
    method_name: helloWorld
    method_args: []

definition:
  references:
  - __type: Reference
    name: executable_contract_constraint
    where:
      all_of:
      - ref.id==this.state.data.executable_contract_id

Its state.data all the information required in order to run cloud method: - id of executable contract - name of cloud method - arguments of cloud method

It should also contain a reference named executable_contract_constraint with a condition looking for an executable contract by its id.

Method execution

Method execution takes two steps: asking regular network for a session (providing request contract) and requesting execution (providing request contract and session).

Asking for a session

Session is initiated by callind ubotCreateSession command on random node providing transaction pack of request contract within packedRequest parameter

    val client = new Client("$topologyname",null,clientKey)
    client.command("ubotCreateSession", "packedRequest", requestContract.getPackedTransaction)

To ensure session is ready client have to query all nodes for its status

val response = client.getClient(i).command("ubotGetSession", "requestId", requestContract.getId)
if(res.getBinderOrThrow("session").getString("state",null) == "OPERATIONAL") {
    // session ready on a node
}

The output of the tool will look like this:

      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    

Once session is ready client can ask for session pool:

val pool = res.getBinderOrThrow("session").getArray("sessionPool")

Pool is an array of numbers of UBot-servers execution is assigned to.

Requesting execution

Connecting to a random UBot-server of a pool:

val n = Do.sample(pool.asJava)
val ubotConnection = client.connectToUbot(n)

Requesting execution:

ubotConnection.command("executeCloudMethod", "contract", requestContract.getPackedTransaction)

Quering execution state:

val response = ubotConnection.command("getState", "requestContractId", requestContract.getId, "getQuanta", true)

The output of the tool will look like this:

      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    

For every Ubot-server of a pool tool prints:

      UBot  1       <<==== UBot server name/number
      FINISHED      <<==== Execution status
    hello world!    <<==== Result
         {}         <<==== Storage contents        
         []         <<==== Cortage storage contents         
       53.30        <<==== The amount of quanta used       

Read / write to Ubot storage

The script

async function write() {
    await writeSingleStorage({foo:1, bar:2})
    return "ok"
}

async function read() {
    let x = await getSingleStorage();
    return x.foo
}

Executable contract (read/write)

    cloud_methods:
      read:
        quorum:
          size: 7
        pool:
          size: 8
      write:
        quorum:
          size: 4
        pool:
          size: 4
        launcher: owner_link

Method write is declared with defining launcher property. This property holds name of the role to be resolved in order to launch cloud method.

Request contract (write)

  - __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

The executable_contract_constraint for such methods should contain additional condition

    "this can_perform ref.state.roles.$LAUNCHER_ROLE_NAME"

Execution of "write" (pool of 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  

Execution of "read" (pool of 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        

Read / write to Ubot cortege storage

The script

async function writeCortege() {

    // get unique number from UBot instances
    let num = await getUBotNumber();            // return unique UBot-server number
    let inPool = await getUBotNumberInPool();   // return number of UBot instance in pool

    //every ubot writes its own value
    await writeMultiStorage({x: num*num+inPool})
    return "ok"
}

async function readCortege() {
    let records = await getMultiStorage();
    var result = 0;
    for (let r of records)
        result += r.x;
    return result
}

Execution of "writeCortege" (pool of 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        

Cortege storage at the end of execution: ~~~ [{result={x=52}, ubotnumber=7}, {result={x=291}, ubotnumber=17}, {result={x=842}, ubotnumber=29}, {result={x=1225}, ubotnumber=35}] ~~~

Execution of "readCortege" (pool of 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            

External HTTP request

Coming soon

Register contract

Code

const ItemState = require("itemstate").ItemState;
const roles = require('roles');
const crypto = require('crypto');

async function registerRoot() {
    try {

        //creates default "pool" contract. With creator, issuer and owner set to the QUORUM OF CURRENT POOL
        //created_at and other things (like salt) that may differ within the pool are synchronized between workers
        let contract = await createPoolContract();

        await contract.seal(true);

        let packed = await contract.getPackedTransaction();

        // register transaction with pool contract
        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);

        //synchronizes created_at and other things (like salt) that may differ within the pool
        //and set creator to the QUORUM OF CURRENT POOL
        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
    }
}

Execution (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   

Execution (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