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, dump_bytes, #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.



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

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

Instance Attribute Details

#private_keyObject (readonly)

Client private key ised in the connection



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

def private_key
  @private_key
end

#sizeObject (readonly)

Discovered network size



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

def size
  @size
end

Instance Method Details

#[](index) ⇒ Connection

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

Returns:

Raises:

  • (IndexError)


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

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:

  • Error if any of the queried nodes will cause an error.



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/universa/client.rb', line 101

def get_state obj, trust: 0.3
  raise ArgumentError, "trust must be in 0.1..0.9 range" if trust < 0.1 || trust > 0.9
  result = Concurrent::IVar.new
  found_error = nil
  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?
        begin
          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
        rescue
          found_error = $!
          result.try_set(nil)
        end
      end
    }
  }
  r = result.value
  found_error != nil and raise found_error
  r
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.



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

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:



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

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



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

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:



140
141
142
143
144
# File 'lib/universa/client.rb', line 140

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