Class: Universa::Client

Inherits:
Object
  • Object
show all
Includes:
Universa
Defined in:
lib/universa/client.rb

Overview

The universa network client. Discover and connects to the universa network, provides consensus operations and all other whole-network related functions.

Constant Summary

Constants included from Universa

ALNUMS, NUMBERS, VERSION

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Universa

#derive_key, #retry_with_timeout

Constructor Details

#initialize(topology: "mainnet", private_key: PrivateKey.new(2048), cache_dir: nil) ⇒ Client

Construct an Universa network client. Bu default, connects to the main network. Perform consensus-based network scanning and saves the current network topology in the cache on the file system, default is under ~/.universa but could be overriden.

If the network topology file is presented but the cached topology is newer, the cached will be used.

The client accepts small network topology changes as long as it still create consensus. Still, too big changes in the network topology might require fresh topology file (or upgrade the gem).

Parameters:

  • topology (String) (defaults to: "mainnet")

    could be name of known network (e.g. mainnet as by default) or path to a .json file containing some network topology, for example, obtained from some external source like telegram channel.

  • private_key (PrivateKey) (defaults to: PrivateKey.new(2048))

    to connect with.

  • cache_dir (String) (defaults to: nil)

    where to store resulting topology. we recommend to leave it as nil.

Raises:

  • if network topology could not be checked/obtained.



43
44
45
46
47
48
# File 'lib/universa/client.rb', line 43

def initialize topology: "mainnet", private_key: PrivateKey.new(2048), cache_dir: nil
  @client = UmiClient.new topology, cache_dir, private_key
  @private_key = PrivateKey
  @size = @client.size
  @connections = (0...@size).map {nil}
end

Instance Attribute Details

#private_keyObject (readonly)

Client private key ised in the connection



24
25
26
# File 'lib/universa/client.rb', line 24

def private_key
  @private_key
end

#sizeObject (readonly)

Discovered network size



21
22
23
# File 'lib/universa/client.rb', line 21

def size
  @size
end

Instance Method Details

#[](index) ⇒ Connection

Get the node connection by its index (0…size).

Returns:

Raises:

  • (IndexError)


52
53
54
55
# File 'lib/universa/client.rb', line 52

def [] index
  raise IndexError if index < 0 || index >= @size
  @connections[index] ||= Connection.new(@client.getClient(index))
end

#get_state(obj, trust: 0.3) ⇒ ContractState

Perform fast consensus state check with a given trust level, as the fraction of the whole network size. It checks the network nodes randomly until get enough positive or negative states. The lover the required trust level is, the faster the answer will be found.

Parameters:

  • obj (Contract | HashId)

    contract to check

  • trust (Object) (defaults to: 0.3)

    level, should be between 0.1 (10% of network) and 0.9 (90% of the network)

Returns:

  • (ContractState)

    of some final node check It does not calculates average time (yet)

Raises:

  • (ArgumentError)


100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/universa/client.rb', line 100

def get_state obj, trust: 0.3
  raise ArgumentError, "trusst must be in 0.1..0.9 range" if trust < 0.1 || trust > 0.9
  result = Concurrent::IVar.new
  negative_votes = Concurrent::AtomicFixnum.new((size * 0.1).round + 1)
  positive_votes = Concurrent::AtomicFixnum.new((size * trust).round)

  # consensus-finding conveyor: we chek connections in batches in parallel until get
  # some consensus. We do not wait until all of them will answer
  (0...size).to_a.shuffle.each {|index|
    Thread.start {
      if result.incomplete?
        if (state = self[index].get_state(obj)).approved?
          result.try_set(state) if positive_votes.decrement < 0
        else
          result.try_set(state) if negative_votes.decrement < 0
        end
      end
    }
  }
  result.value
end

#is_approved?(obj, trust: 0.3) ⇒ Boolean

Perform fast consensus state check with a given trust level, determining whether the item is approved or not. Blocks for 1 minute or until the network solution will be collected for a given trust level.

Parameters:

  • obj (Contract | HashId | String | Binary)

    contract to check

  • trust (Object) (defaults to: 0.3)

    level, should be between 0.1 (10% of network) and 0.9 (90% of the network)

Returns:

  • (Boolean)

    true if the contract state is approved by the network with a given trust level, false otherwise.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/universa/client.rb', line 76

def is_approved? obj, trust: 0.3
  hash_id = case obj
              when HashId
                obj
              when Contract
                obj.hash_id
              when String
                if obj.encoding == Encoding::ASCII_8BIT
                  HashId.from_digest(obj)
                else
                  HashId.from_string(obj)
                end
              else
                raise ArgumentError "wrong type of object to check approval"
            end
  @client.isApprovedByNetwork(hash_id, trust.to_f, 60000)
end

#random_connectionConnection

Get the random node connection

Returns:



59
60
61
# File 'lib/universa/client.rb', line 59

def random_connection
  self[rand(0...size)]
end

#random_connections(number) ⇒ Array(Connection)

Get several random connections

Parameters:

  • number (Numeric)

    of connections to get

Returns:

  • (Array(Connection))

    array of connections to random (non repeating) nodes



66
67
68
# File 'lib/universa/client.rb', line 66

def random_connections number
  (0...size).to_a.sample(number).map {|n| self[n]}
end

#register_single(contract, timeout: 45, max_retries: 3) ⇒ ContractState

Register a single contract (on private network or if you have white key allowing free operations) on a random node. Client must check returned contract state. It requires “open” network or special key that has a right to register contracts without payment.

When retrying, randpm nodes are selected.

Parameters:

Returns:



131
132
133
134
135
# File 'lib/universa/client.rb', line 131

def register_single(contract, timeout: 45, max_retries: 3)
  retry_with_timeout(timeout, max_retries) {
    ContractState.new(random_connection.register_single(contract, timeout / max_retries * 1000 - 100))
  }
end