nanook 2.5.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Nanook
4
+ Error = Class.new(StandardError)
5
+
6
+ ConnectionError = Class.new(Error)
7
+ NanoUnitError = Class.new(Error)
8
+ NodeRpcError = Class.new(Error)
9
+ NodeRpcConfigurationError = Class.new(NodeRpcError)
10
+ end
data/lib/nanook/node.rb CHANGED
@@ -1,5 +1,8 @@
1
- class Nanook
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'util'
2
4
 
5
+ class Nanook
3
6
  # The <tt>Nanook::Node</tt> class contains methods to manage your nano
4
7
  # node and query its data of the nano network.
5
8
  #
@@ -25,6 +28,7 @@ class Nanook
25
28
  # rpc_conn = Nanook::Rpc.new
26
29
  # node = Nanook::Node.new(rpc_conn)
27
30
  class Node
31
+ include Nanook::Util
28
32
 
29
33
  def initialize(rpc)
30
34
  @rpc = rpc
@@ -38,140 +42,125 @@ class Nanook
38
42
  #
39
43
  # @return [Integer] number of accounts with _open_ blocks.
40
44
  def account_count
41
- rpc(:frontier_count)[:count]
45
+ rpc(:frontier_count, _access: :count)
42
46
  end
43
- alias_method :frontier_count, :account_count
47
+ alias frontier_count account_count
44
48
 
45
49
  # The count of all blocks downloaded to the node, and
46
50
  # blocks still to be synchronized by the node.
47
51
  #
48
52
  # ==== Example:
49
53
  #
50
- #
54
+ # {
55
+ # count: 100,
56
+ # unchecked: 10,
57
+ # cemented: 25
58
+ # }
51
59
  #
52
60
  # @return [Hash{Symbol=>Integer}] number of blocks and unchecked
53
61
  # synchronizing blocks
54
62
  def block_count
55
- rpc(:block_count)
63
+ rpc(:block_count, _coerce: Hash)
56
64
  end
57
65
 
58
- # The count of all known blocks by their type.
59
- #
60
- # ==== Example:
61
- #
62
- # node.block_count_by_type
66
+ # Tells the node to send a keepalive packet to a specific IP address and port.
63
67
  #
64
- # Example response:
65
- #
66
- # {
67
- # send: 1000,
68
- # receive: 900,
69
- # open: 900,
70
- # change: 50
71
- # }
72
- #
73
- # @return [Hash{Symbol=>Integer}] number of blocks by type
74
- def block_count_by_type
75
- rpc(:block_count_type)
68
+ # @return [Boolean] indicating if the action was successful
69
+ def keepalive(address:, port:)
70
+ rpc(:keepalive, address: address, port: port).key?(:started)
76
71
  end
77
- alias_method :block_count_type, :block_count_by_type
78
72
 
79
73
  # Initialize bootstrap to a specific IP address and port.
80
74
  #
81
75
  # @return [Boolean] indicating if the action was successful
82
76
  def bootstrap(address:, port:)
83
- rpc(:bootstrap, address: address, port: port).has_key?(:success)
77
+ rpc(:bootstrap, address: address, port: port).key?(:success)
84
78
  end
85
79
 
86
80
  # Initialize multi-connection bootstrap to random peers
87
81
  #
88
- # @return [Boolean] indicating if the action was successful
89
- def bootstrap_any
90
- rpc(:bootstrap_any).has_key?(:success)
91
- end
92
-
93
- # Initialize lazy bootstrap with given block hash
94
- #
95
- # @param hash [String]
96
- # @param force [Boolean] False by default. Manually force closing
82
+ # @param account [Nanook::Account] False by default. Manually force closing
97
83
  # of all current bootstraps
98
84
  # @return [Boolean] indicating if the action was successful
99
- def bootstrap_lazy(hash, force: false)
100
- rpc(:bootstrap_lazy, hash: hash, force: force)[:started] == 1
85
+ def bootstrap_any(account: nil)
86
+ params = {
87
+ account: account
88
+ }.compact
89
+
90
+ rpc(:bootstrap_any, params).key?(:success)
101
91
  end
102
92
 
103
- # Returning status of current bootstrap attempt for debug purposes only.
104
- # This call is for internal diagnostics/debug purposes only.
105
- # Do not rely on this interface being stable and do not use in a
106
- # production system.
93
+ # Initialize lazy bootstrap with given block hash.
94
+ # Response includes whether new election was started and whether a
95
+ # new lazy key_inserted was successful.
107
96
  #
108
97
  # ==== Example:
109
98
  #
110
- # node.bootstrap_status
99
+ # node.bootstrap_lazy
111
100
  #
112
101
  # Example response:
113
102
  #
114
103
  # {
115
- # clients: 5790,
116
- # pulls: 141065,
117
- # pulling: 3,
118
- # connections: 16,
119
- # idle: 0,
120
- # target_connections: 64,
121
- # total_blocks: 536820,
122
- # lazy_mode: true,
123
- # lazy_blocks: 423388,
124
- # lazy_state_unknown: 2,
125
- # lazy_balances: 0,
126
- # lazy_pulls: 0,
127
- # lazy_stopped: 644,
128
- # lazy_keys: 449,
129
- # lazy_key_1: "A86EB2B479AAF3CD531C8356A1FBE3CB500DFBF5BF292E5E6B8D1048DE199C32"
104
+ # "started": true,
105
+ # "key_inserted": false
130
106
  # }
131
107
  #
132
- # @return [Hash{Symbol=>String|Integer|Boolean}]
133
- def bootstrap_status
134
- rpc(:bootstrap_status)
108
+ # @param hash [String]
109
+ # @param force [Boolean] False by default. Manually force closing
110
+ # of all current bootstraps
111
+ # @return [Hash{Symbol=>Boolean}] indicating if the action was successful
112
+ def bootstrap_lazy(hash, force: false)
113
+ response = rpc(:bootstrap_lazy, hash: hash, force: force)
114
+ values = response.map { |k, v| [k, v == 1] }
115
+
116
+ Hash[values]
135
117
  end
136
118
 
137
- # This call is for internal diagnostics/debug purposes only.
138
- # Do not rely on this interface being stable and do not use in a
139
- # production system.
119
+ # Returns information about node elections settings and observed network state:
140
120
  #
141
- # Returns block and tally weight (in raw) election duration (in
142
- # milliseconds), election confirmation timestamp for recent elections
143
- # winners.
121
+ # - `quorum_delta`: delta tally required to rollback block
122
+ # - `online_weight_quorum_percent`: percentage of online weight for delta
123
+ # - `online_weight_minimum`: minimum online weight to confirm block
124
+ # - `online_stake_total`: currently observed online total weight
125
+ # - `peers_stake_total`: known peers total weight
126
+ # - `peers_stake_required`: effective stake needed from directly connected peers for quorum
144
127
  #
145
128
  # ==== Example:
146
129
  #
147
- # node.confirmation_history
130
+ # node.confirmation_quorum
148
131
  #
149
132
  # Example response:
150
133
  #
151
- # [
152
- # {
153
- # block: "EA70B32C55C193345D625F766EEA2FCA52D3F2CCE0B3A30838CC543026BB0FEA",
154
- # tally: 80394786589602980996311817874549318248,
155
- # duration: 4000,
156
- # time: 1544819986,
157
- # },
158
- # {
159
- # block: "F2F8DA6D2CA0A4D78EB043A7A29E12BDE5B4CE7DE1B99A93A5210428EE5B8667",
160
- # tally: 68921714529890443063672782079965877749,
161
- # duration: 6000,
162
- # time: 1544819988,
163
- # }
164
- # ]
134
+ # {
135
+ # quorum_delta: 43216377.43025059,
136
+ # online_weight_quorum_percent: 50,
137
+ # online_weight_minimum: 60000000.0,
138
+ # online_stake_total: 86432754.86050119,
139
+ # peers_stake_total: 84672338.52479072,
140
+ # peers_stake_required: 60000000.0
141
+ # }
165
142
  #
166
143
  # @return [Hash{Symbol=>String|Integer}]
167
- def confirmation_history
168
- rpc(:confirmation_history)[:confirmations].map do |history|
169
- # Rename hash key to block
170
- block = history.delete(:hash)
171
- {block: block}.merge(history)
172
- end
144
+ # @raise [Nanook::NanoUnitError] if `unit` is invalid
145
+ def confirmation_quorum(unit: Nanook.default_unit)
146
+ validate_unit!(unit)
147
+
148
+ response = rpc(:confirmation_quorum, _coerce: Hash)
149
+
150
+ return response unless unit == :nano
151
+
152
+ response[:quorum_delta] = raw_to_NANO(response[:quorum_delta])
153
+ response[:online_weight_minimum] = raw_to_NANO(response[:online_weight_minimum])
154
+ response[:online_stake_total] = raw_to_NANO(response[:online_stake_total])
155
+ response[:peers_stake_total] = raw_to_NANO(response[:peers_stake_total])
156
+ response[:peers_stake_required] = raw_to_NANO(response[:peers_stake_required])
157
+
158
+ response.compact
173
159
  end
174
160
 
161
+ # Note: This RPC call is deprecated as of v22 of the node software.
162
+ # https://docs.nano.org/releases/release-v22-0/
163
+ #
175
164
  # Returns the difficulty values (16 hexadecimal digits string, 64 bit)
176
165
  # for the minimum required on the network (network_minimum) as well
177
166
  # as the current active difficulty seen on the network (network_current,
@@ -205,22 +194,35 @@ class Nanook
205
194
  # that the first value is the most recent sample.
206
195
  # @return [Hash{Symbol=>String|Float|Array}]
207
196
  def difficulty(include_trend: false)
208
- rpc(:active_difficulty, include_trend: include_trend).tap do |response|
197
+ rpc(:active_difficulty, include_trend: include_trend, _coerce: Hash).tap do |response|
209
198
  response[:multiplier] = response[:multiplier].to_f
210
199
 
211
- if response.key?(:difficulty_trend)
212
- response[:difficulty_trend].map!(&:to_f)
213
- end
200
+ response[:difficulty_trend].map!(&:to_f) if response.key?(:difficulty_trend)
214
201
  end
215
202
  end
216
203
 
217
204
  # @return [String]
218
- def inspect
219
- "#{self.class.name}(object_id: \"#{"0x00%x" % (object_id << 1)}\")"
205
+ def to_s
206
+ self.class.name
220
207
  end
208
+ alias inspect to_s
221
209
 
210
+ # Returns peers information.
211
+ #
212
+ # Example response:
213
+ #
214
+ # {
215
+ # :"[::ffff:104.131.102.132]:7075" => {
216
+ # protocol_version: 20,
217
+ # node_id: "node_1y7j5rdqhg99uyab1145gu3yur1ax35a3b6qr417yt8cd6n86uiw3d4whty3",
218
+ # type: "udp"
219
+ # },
220
+ # :"[::ffff:104.131.114.102]:7075" => { ... }
221
+ # }
222
+ #
223
+ # @return [Hash{Symbol=>Hash{Symbol=>Integer|String}}]
222
224
  def peers
223
- rpc(:peers)[:peers]
225
+ rpc(:peers, peer_details: true, _access: :peers, _coerce: Hash)
224
226
  end
225
227
 
226
228
  # All representatives and their voting weight.
@@ -232,59 +234,86 @@ class Nanook
232
234
  # Example response:
233
235
  #
234
236
  # {
235
- # nano_1111111111111111111111111111111111111111111111111117353trpda: 3822372327060170000000000000000000000,
236
- # nano_1111111111111111111111111111111111111111111111111awsq94gtecn: 30999999999999999999999999000000,
237
- # nano_114nk4rwjctu6n6tr6g6ps61g1w3hdpjxfas4xj1tq6i8jyomc5d858xr1xi: 0
237
+ # Nanook::Account: 3822372327060170000000000000000000000,
238
+ # Nanook::Account: 30999999999999999999999999000000,
239
+ # Nanook::Account: 0
238
240
  # }
239
241
  #
240
- # @return [Hash{Symbol=>Integer}] known representatives and their voting weight
242
+ # @return [Hash{Nanook::Account=>Float|Integer}] known representatives and their voting weight
243
+ # @raise [Nanook::NanoUnitError] if `unit` is invalid
241
244
  def representatives(unit: Nanook.default_unit)
242
- unless Nanook::UNITS.include?(unit)
243
- raise ArgumentError.new("Unsupported unit: #{unit}")
244
- end
245
+ validate_unit!(unit)
245
246
 
246
- response = rpc(:representatives)[:representatives]
247
- return response if unit == :raw
247
+ response = rpc(:representatives, _access: :representatives, _coerce: Hash)
248
248
 
249
- r = response.map do |account_id, balance|
250
- balance = Nanook::Util.raw_to_NANO(balance)
249
+ r = response.map do |account_id, weight|
250
+ weight = raw_to_NANO(weight) if unit == :nano
251
251
 
252
- [account_id, balance]
252
+ [as_account(account_id), weight]
253
253
  end
254
254
 
255
- Hash[r].to_symbolized_hash
255
+ Hash[r]
256
256
  end
257
257
 
258
- # All online representatives that have voted recently. Note, due to the
259
- # design of the nano RPC, this method cannot return the voting weight
260
- # of the representatives.
258
+ # All online representatives that have voted recently and their weight.
261
259
  #
262
260
  # ==== Example:
263
261
  #
264
- # node.representatives_online # => ["nano_111...", "nano_222"]
262
+ # node.representatives_online # => [Nanook::Account, ...]
265
263
  #
266
- # @return [Array<String>] array of representative account ids
264
+ # @return [Nanook::Account] array of representative accounts
267
265
  def representatives_online
268
- rpc(:representatives_online)[:representatives].keys.map(&:to_s)
266
+ rpc(:representatives_online, _access: :representatives, _coerce: Array).map do |representative|
267
+ as_account(representative)
268
+ end
269
+ end
270
+
271
+ # Tells the node to look for any account in all available wallets.
272
+ #
273
+ # ==== Example:
274
+ #
275
+ # node.search_pending #=> true
276
+ # @return [Boolean] indicates if the action was successful
277
+ def search_pending
278
+ rpc(:search_pending_all).key?(:success)
269
279
  end
270
280
 
271
281
  # Safely shuts down the node.
272
282
  #
273
283
  # @return [Boolean] indicating if action was successful
274
284
  def stop
275
- rpc(:stop).has_key?(:success)
285
+ rpc(:stop).key?(:success)
276
286
  end
277
287
 
278
288
  # @param limit [Integer] number of synchronizing blocks to return
289
+ # @param unit (see Nanook::Account#balance)
290
+ #
279
291
  # @return [Hash{Symbol=>String}] information about the synchronizing blocks for this node
280
- def synchronizing_blocks(limit: 1000)
281
- response = rpc(:unchecked, count: limit)[:blocks]
282
- response = response.map do |block, info|
283
- [block, JSON.parse(info).to_symbolized_hash]
292
+ # @raise [Nanook::NanoUnitError] if `unit` is invalid
293
+ def synchronizing_blocks(limit: 1000, unit: Nanook.default_unit)
294
+ validate_unit!(unit)
295
+
296
+ params = {
297
+ count: limit,
298
+ json_block: true,
299
+ _access: :blocks,
300
+ _coerce: Hash
301
+ }
302
+
303
+ response = rpc(:unchecked, params).map do |block, info|
304
+ info[:account] = as_account(info[:account])
305
+ info[:link_as_account] = as_account(info[:link_as_account])
306
+ info[:representative] = as_account(info[:representative])
307
+ info[:previous] = as_block(info[:previous])
308
+ info[:link] = as_block(info[:link])
309
+ info[:balance] = raw_to_NANO(info[:balance]) if unit == :nano
310
+
311
+ [as_block(block), info]
284
312
  end
285
- Hash[response.sort].to_symbolized_hash
313
+
314
+ Hash[response]
286
315
  end
287
- alias_method :unchecked, :synchronizing_blocks
316
+ alias unchecked synchronizing_blocks
288
317
 
289
318
  # The percentage completeness of the synchronization process for
290
319
  # your node as it downloads the nano ledger. Note, it's normal for
@@ -295,11 +324,11 @@ class Nanook
295
324
  # @return [Float] the percentage completeness of the synchronization
296
325
  # process for your node
297
326
  def sync_progress
298
- response = rpc(:block_count)
327
+ response = rpc(:block_count, _coerce: Hash)
299
328
 
300
329
  count = response[:count]
301
330
  unchecked = response[:unchecked]
302
- total = count + unchecked
331
+ total = count + unchecked
303
332
 
304
333
  count.to_f * 100 / total.to_f
305
334
  end
@@ -308,29 +337,57 @@ class Nanook
308
337
  #
309
338
  # @return [Integer] seconds of uptime
310
339
  def uptime
311
- rpc(:uptime)['seconds']
340
+ rpc(:uptime, _access: :seconds, _coerce: Hash)
312
341
  end
313
342
 
314
- # This method is deprecated and will be removed in 3.0, as a node never
315
- # reaches 100% synchronization.
343
+ # Sets the receive minimum for wallets on the node. The value is in +Nano+ by default.
344
+ # To specify an amount in +raw+, pass the argument +unit: :raw+.
345
+ #
346
+ # ==== Example:
347
+ #
348
+ # account.change_receive_minimum(0.01) # true
316
349
  #
317
- # @return [Boolean] signalling if this node ever reaches 100% synchronized
318
- def synced?
319
- warn "[DEPRECATION] `synced?` is deprecated and will be removed in 3.0"
320
- rpc(:block_count)[:unchecked] == 0
350
+ # @return [Boolean] true if the action was successful
351
+ # @param minimum Amount to set as the receive minimum
352
+ # @param unit optional. Specify +raw+ if you want to set the amount in +raw+. (See Nanook::Account#balance)
353
+ # @raise [Nanook::NanoUnitError] if `unit` is invalid
354
+ def change_receive_minimum(minimum, unit: Nanook.default_unit)
355
+ validate_unit!(unit)
356
+
357
+ minimum = NANO_to_raw(minimum) if unit == :nano
358
+
359
+ rpc(:receive_minimum_set, amount: minimum).key?(:success)
360
+ end
361
+
362
+ # Returns receive minimum for wallets on the node.
363
+ #
364
+ # ==== Example:
365
+ #
366
+ # account.receive_minimum # => 0.01
367
+ #
368
+ # @return [Integer|Float] the receive minimum
369
+ # @param unit (see Nanook::Account#balance)
370
+ # @raise [Nanook::NanoUnitError] if `unit` is invalid
371
+ def receive_minimum(unit: Nanook.default_unit)
372
+ validate_unit!(unit)
373
+
374
+ amount = rpc(:receive_minimum, _access: :amount)
375
+
376
+ return amount unless unit == :nano
377
+
378
+ raw_to_NANO(amount)
321
379
  end
322
380
 
323
381
  # @return [Hash{Symbol=>Integer|String}] version information for this node
324
382
  def version
325
- rpc(:version)
383
+ rpc(:version, _coerce: Hash)
326
384
  end
327
- alias_method :info, :version
385
+ alias info version
328
386
 
329
387
  private
330
388
 
331
- def rpc(action, params={})
389
+ def rpc(action, params = {})
332
390
  @rpc.call(action, params)
333
391
  end
334
-
335
392
  end
336
393
  end