nanook 2.5.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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