universa 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +7 -3
- data/exe/universa +1 -1
- data/lib/universa/client.rb +228 -0
- data/lib/universa/contract.rb +2 -1
- data/lib/universa/errors.rb +4 -0
- data/lib/universa/umi.rb +3 -1
- data/lib/universa/version.rb +1 -1
- data/lib/universa.rb +1 -0
- data/universa.gemspec +3 -0
- metadata +45 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ff6ba5e26ea3e8ef5c6f66da7c2e13045f752e251ff8783b990376b06f95af31
|
4
|
+
data.tar.gz: 543afbaaeb3813cfe082171469ef6997dfcd67ad96de62d434556c673d62d198
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 576c85a3a975de182195866635820dcbbb2a1dc531526440aee8dbb8d35d4783db45b2cec8b0c1ac7908caaa7b57de7c4bc893412dee60e84425bd047511e422
|
7
|
+
data.tar.gz: 677b361b35bcfe73ed8005735123ab6eea59a3077d41646a3dbccea9bd2885169d416dcf7f7929128e46e64347b4e0d9780c4732d9470e13985b839d36101241
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# Universa
|
2
2
|
|
3
|
-
> Alfa state: direct access to Java API ready for test
|
3
|
+
> Alfa state: direct full access to Java API ready for test, started adapter of remote objects - syntax sugar
|
4
|
+
for direct access to remote objects.
|
4
5
|
|
5
6
|
This is an under-construction official gem from [Universa][universa] to facilitate access to the
|
6
7
|
Java library using Universa's UMI protocol.
|
@@ -50,11 +51,14 @@ contract_id = contract.get_id.to_base64_string
|
|
50
51
|
|
51
52
|
```
|
52
53
|
|
54
|
+
## Docs and resources
|
55
|
+
|
53
56
|
for more information see:
|
54
57
|
|
55
58
|
- [Universa gem page](https://kb.universa.io/universa_ruby_gem/131) in the Universa Knowledge Base.
|
56
59
|
- Universa Java API: https://kb.universa.io/general_java_api/5
|
57
60
|
- Universa UMI server: https://kb.universa.io/umi_protocol/98
|
61
|
+
- Ruby [docs online](https://kb.universa.io/system/static/gem_universa/)
|
58
62
|
- Farcall [gem](https://github.com/sergeych/farcall) and [protocol](https://github.com/sergeych/farcall/wiki).
|
59
63
|
|
60
64
|
### Use UMI service (alfa state)
|
@@ -63,7 +67,7 @@ The Universa::Service greatly simplify work taking all boilerplate. Just create
|
|
63
67
|
as is they are local:
|
64
68
|
|
65
69
|
```ruby
|
66
|
-
key = PrivateKey
|
70
|
+
key = PrivateKey.new(open('mykey.private.unikey', 'rb').read)
|
67
71
|
contract = Contract.new(key)
|
68
72
|
|
69
73
|
contract.seal()
|
@@ -91,4 +95,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
91
95
|
|
92
96
|
Everyone interacting in the Universa project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/universa/blob/master/CODE_OF_CONDUCT.md).
|
93
97
|
|
94
|
-
[universa]:http://universa.io
|
98
|
+
[universa]:http://universa.io
|
data/exe/universa
CHANGED
@@ -0,0 +1,228 @@
|
|
1
|
+
require 'boss-protocol'
|
2
|
+
require 'open-uri'
|
3
|
+
require 'concurrent'
|
4
|
+
|
5
|
+
module Universa
|
6
|
+
|
7
|
+
using Universa
|
8
|
+
|
9
|
+
def retry_with_timeout(max_timeout = 15, max_times = 3, &block)
|
10
|
+
attempt = 0
|
11
|
+
Timeout::timeout(max_timeout, &block)
|
12
|
+
rescue
|
13
|
+
attempt += 1
|
14
|
+
puts "timeout: retry (#$!): #{attempt}"
|
15
|
+
retry if attempt < max_times
|
16
|
+
raise
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
module Parallel
|
21
|
+
|
22
|
+
class ParallelEnumerable < SimpleDelegator
|
23
|
+
include Concurrent
|
24
|
+
|
25
|
+
@@pool = CachedThreadPool.new
|
26
|
+
|
27
|
+
def each_with_index &block
|
28
|
+
latch = CountDownLatch.new(size)
|
29
|
+
__getobj__.each_with_index {|x, index|
|
30
|
+
@@pool << -> {
|
31
|
+
begin
|
32
|
+
block.call(x, index)
|
33
|
+
rescue
|
34
|
+
$!.print_stack_trace
|
35
|
+
ensure
|
36
|
+
latch.count_down
|
37
|
+
end
|
38
|
+
}
|
39
|
+
}
|
40
|
+
latch.wait
|
41
|
+
end
|
42
|
+
|
43
|
+
def each &block
|
44
|
+
each_with_index {|x, i| block.call(x)}
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def map &block
|
49
|
+
result = size.times.map {nil}
|
50
|
+
each_with_index {|value, i|
|
51
|
+
result[i] = block.call(value)
|
52
|
+
}
|
53
|
+
result.par
|
54
|
+
end
|
55
|
+
|
56
|
+
alias_method :collect, :map
|
57
|
+
end
|
58
|
+
|
59
|
+
refine Enumerable do
|
60
|
+
def par
|
61
|
+
is_a?(ParallelEnumerable) ? self : ParallelEnumerable.new(self)
|
62
|
+
end
|
63
|
+
|
64
|
+
def group_by &block
|
65
|
+
result = {}
|
66
|
+
each {|value|
|
67
|
+
new_key = block.call(value)
|
68
|
+
(result[new_key] ||= []) << value
|
69
|
+
}
|
70
|
+
result
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
using Parallel
|
78
|
+
|
79
|
+
# Universa network client reads current network configuration and provides access to each node independently
|
80
|
+
# and also implement newtor-wide procedures.
|
81
|
+
class Client
|
82
|
+
|
83
|
+
# Create client
|
84
|
+
# @param [PrivateKey] private_key to connect with. Generates new one if omitted.
|
85
|
+
def initialize private_key = nil
|
86
|
+
@connection_key = private_key
|
87
|
+
scan_network()
|
88
|
+
end
|
89
|
+
|
90
|
+
# Number of accessible nodes
|
91
|
+
def size
|
92
|
+
@nodes.size
|
93
|
+
end
|
94
|
+
|
95
|
+
# private key used by the connection (might be generated)
|
96
|
+
def private_key
|
97
|
+
@connection_key ||= PrivateKey.new(2048)
|
98
|
+
end
|
99
|
+
|
100
|
+
# @return [Connection] random connection
|
101
|
+
def random_connection
|
102
|
+
@nodes.sample
|
103
|
+
end
|
104
|
+
|
105
|
+
# @return [Array(Connection)] array of count randomly selected connections
|
106
|
+
def random_connections count = 1
|
107
|
+
@nodes.sample(count)
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
# Rescan the network collecting the networ map comparing results from random 70% of nodes.
|
114
|
+
def scan_network
|
115
|
+
# Todo: cache known nodes
|
116
|
+
root_nodes = (1..30).map {|n| "http://node-#{n}-com.universa.io:8080/network"}
|
117
|
+
|
118
|
+
# We scan random 70% for consensus
|
119
|
+
n = root_nodes.size * 0.7
|
120
|
+
|
121
|
+
candidates = {}
|
122
|
+
root_nodes.sample(n).par.each {|path|
|
123
|
+
retry_with_timeout(5, 3) {
|
124
|
+
SmartHash.new(Boss.unpack open(path).read).response.nodes.each {|data|
|
125
|
+
ni = NodeInfo.new(data)
|
126
|
+
(candidates[ni] ||= ni).increment_rate
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
nodes = candidates.values.group_by(&:url)
|
131
|
+
.transform_values!(&:sort)
|
132
|
+
# We roughly assume the full network size as:
|
133
|
+
network_max_size = nodes.size
|
134
|
+
nodes.each {|k, v|
|
135
|
+
puts "#{k}: (#{v.size}) #{v.map(&:rate)}}"
|
136
|
+
}
|
137
|
+
# Refine result: takes most voted nodes and only these with 80% consensus
|
138
|
+
# and map it to Connection objects
|
139
|
+
min_rate = n * 0.8
|
140
|
+
@nodes = nodes.values.map {|v| v[-1]}.delete_if {|v| v.rate < min_rate}
|
141
|
+
.map {|ni| Connection.new(self, ni)}
|
142
|
+
raise NetworkError, "network is not ready" if @nodes.size < network_max_size * 0.9
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# The node information
|
147
|
+
class NodeInfo
|
148
|
+
attr :number, :packed_key, :url
|
149
|
+
|
150
|
+
def initialize(data)
|
151
|
+
@data, @number, @url, @packed_key = data, data.number, data.url, data.packed_key
|
152
|
+
@rate = Concurrent::AtomicFixnum.new
|
153
|
+
end
|
154
|
+
|
155
|
+
def rate
|
156
|
+
@rate.value
|
157
|
+
end
|
158
|
+
|
159
|
+
def increment_rate
|
160
|
+
@rate.increment
|
161
|
+
end
|
162
|
+
|
163
|
+
def == other
|
164
|
+
# number == other.number && packed_key == other.packed_key && url == other.url
|
165
|
+
url == other&.url && packed_key == other&.packed_key && url == other&.url
|
166
|
+
end
|
167
|
+
|
168
|
+
def hash
|
169
|
+
@url.hash + @packed_key.hash
|
170
|
+
end
|
171
|
+
|
172
|
+
def eql?(other)
|
173
|
+
self == other
|
174
|
+
end
|
175
|
+
|
176
|
+
def < other
|
177
|
+
rate < other.rate
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
# Access to the single node using universa client protocol.
|
183
|
+
#
|
184
|
+
class Connection
|
185
|
+
# create connection for a given clietn. Don't call it direcly, use
|
186
|
+
# {Client.random_connection} or {Client.random_connections} instead. The client implements
|
187
|
+
# lazy initialization so time-consuming actual connection will be postponed until
|
188
|
+
# needed.
|
189
|
+
#
|
190
|
+
# @param [Client] client instance to be bound to
|
191
|
+
# @param [NodeInfo] node_info to connect to
|
192
|
+
def initialize(client, node_info)
|
193
|
+
@client, @node_info = client, node_info
|
194
|
+
end
|
195
|
+
|
196
|
+
# executes ping. Just to ensure connection is alive. Node answers 'sping' => 'spong' hash.
|
197
|
+
# 's' states that secure layer of client protocol is used, e.g. with mutual identification and
|
198
|
+
# ciphering.
|
199
|
+
def ping
|
200
|
+
execute(:sping)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Execute Universa Node client protocol command with optional keyword arguments that will be passed
|
204
|
+
# to the node.
|
205
|
+
#
|
206
|
+
# @param [String|Symbol] name of the command
|
207
|
+
# @return [SmartHash] with the command result
|
208
|
+
def execute name, **kwargs
|
209
|
+
Service.umi.with_trace {
|
210
|
+
connection.command name.to_s, *kwargs.to_a.flatten
|
211
|
+
}
|
212
|
+
end
|
213
|
+
|
214
|
+
protected
|
215
|
+
|
216
|
+
def connection
|
217
|
+
@connection ||= retry_with_timeout(15, 3) {
|
218
|
+
Service.umi.instantiate "com.icodici.universa.node2.network.Client",
|
219
|
+
@node_info.url,
|
220
|
+
@client.private_key,
|
221
|
+
nil,
|
222
|
+
false
|
223
|
+
}
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
data/lib/universa/contract.rb
CHANGED
data/lib/universa/errors.rb
CHANGED
@@ -4,6 +4,10 @@ module Universa
|
|
4
4
|
class Error < IOError
|
5
5
|
end
|
6
6
|
|
7
|
+
# Genegal error with universa network
|
8
|
+
class NetworkError < Error
|
9
|
+
end
|
10
|
+
|
7
11
|
# references from different {UMI} instances are mixed together
|
8
12
|
class InterchangeError < Error
|
9
13
|
# create instance optionally overriding message
|
data/lib/universa/umi.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'open3'
|
2
2
|
require 'farcall'
|
3
|
+
require 'set'
|
3
4
|
require 'base64'
|
4
5
|
require 'weakref'
|
5
6
|
require_relative 'weak_reference'
|
@@ -137,8 +138,9 @@ module Universa
|
|
137
138
|
# These calls could be nested, on exit it restores previous trace state
|
138
139
|
def with_trace &block
|
139
140
|
current_state, @trace = @trace, true
|
140
|
-
block.call()
|
141
|
+
result = block.call()
|
141
142
|
@trace = current_state
|
143
|
+
result
|
142
144
|
end
|
143
145
|
|
144
146
|
private
|
data/lib/universa/version.rb
CHANGED
data/lib/universa.rb
CHANGED
data/universa.gemspec
CHANGED
@@ -31,6 +31,9 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.require_paths = ["lib"]
|
32
32
|
|
33
33
|
spec.add_dependency "farcall", ">= 0.4.6"
|
34
|
+
spec.add_dependency "boss-protocol", ">= 1.5.0"
|
35
|
+
spec.add_dependency "concurrent-ruby", ">= 1.0.5"
|
36
|
+
spec.add_dependency "concurrent-ruby-ext"
|
34
37
|
|
35
38
|
spec.add_development_dependency "bundler", "~> 1.16"
|
36
39
|
spec.add_development_dependency "rake", "~> 10.0"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: universa
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- sergeych
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-11-
|
11
|
+
date: 2018-11-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: farcall
|
@@ -24,6 +24,48 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 0.4.6
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: boss-protocol
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.5.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.5.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: concurrent-ruby
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.0.5
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.0.5
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: concurrent-ruby-ext
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
27
69
|
- !ruby/object:Gem::Dependency
|
28
70
|
name: bundler
|
29
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -111,6 +153,7 @@ files:
|
|
111
153
|
- bin/umi/lib/org.yaml.snakeyaml-1.18.jar
|
112
154
|
- exe/universa
|
113
155
|
- lib/universa.rb
|
156
|
+
- lib/universa/client.rb
|
114
157
|
- lib/universa/contract.rb
|
115
158
|
- lib/universa/errors.rb
|
116
159
|
- lib/universa/keys.rb
|