nanook 2.5.1 → 3.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.
data/lib/nanook/block.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::Block</tt> class contains methods to discover
4
7
  # publicly-available information about blocks on the nano network.
5
8
  #
@@ -17,21 +20,38 @@ class Nanook
17
20
  # rpc_conn = Nanook::Rpc.new
18
21
  # block = Nanook::Block.new(rpc_conn, "FBF8B0E...")
19
22
  class Block
23
+ include Nanook::Util
20
24
 
21
25
  def initialize(rpc, block)
22
26
  @rpc = rpc
23
- @block = block
24
- block_required! # All methods expect a block
27
+ @block = block.to_s
25
28
  end
26
29
 
27
- # Returns the {Nanook::Account} of the block.
30
+ # Returns the block hash id.
28
31
  #
29
32
  # ==== Example:
30
- # block.account # => Nanook::Account
31
33
  #
32
- # @return [Nanook::Account] the account of the block
33
- def account
34
- Nanook::Account.new(@rpc, rpc(:block_account, :hash)[:account])
34
+ # block.id #=> "FBF8B0E..."
35
+ #
36
+ # @return [String] the block hash id
37
+ def id
38
+ @block
39
+ end
40
+
41
+ # @param other [Nanook::Block] block to compare
42
+ # @return [Boolean] true if blocks are equal
43
+ def ==(other)
44
+ other.class == self.class &&
45
+ other.id == id
46
+ end
47
+ alias eql? ==
48
+
49
+ # The hash value is used along with #eql? by the Hash class to determine if two objects
50
+ # reference the same hash key.
51
+ #
52
+ # @return [Integer]
53
+ def hash
54
+ id.hash
35
55
  end
36
56
 
37
57
  # Stop generating work for a block.
@@ -59,58 +79,36 @@ class Nanook
59
79
  #
60
80
  # ==== Example reponse:
61
81
  #
62
- # [
63
- # "36A0FB717368BA8CF8D255B63DC207771EABC6C6FFC22A7F455EC2209464897E",
64
- # "FBF8B0E6623A31AB528EBD839EEAA91CAFD25C12294C46754E45FD017F7939EB"
65
- # ]
82
+ # [Nanook::Block, ...]
66
83
  #
67
84
  # @param limit [Integer] maximum number of block hashes to return (default is 1000)
68
85
  # @param offset [Integer] return the account chain block hashes offset by the specified number of blocks (default is 0)
69
86
  def chain(limit: 1000, offset: 0)
70
- response = rpc(:chain, :block, count: limit, offset: offset)[:blocks]
71
- Nanook::Util.coerce_empty_string_to_type(response, Array)
87
+ params = {
88
+ count: limit,
89
+ offset: offset,
90
+ _access: :blocks,
91
+ _coerce: Array
92
+ }
93
+
94
+ rpc(:chain, :block, params).map do |block|
95
+ as_block(block)
96
+ end
72
97
  end
73
- alias_method :ancestors, :chain
98
+ alias ancestors chain
74
99
 
75
100
  # Request confirmation for a block from online representative nodes.
76
101
  # Will return immediately with a boolean to indicate if the request for
77
102
  # confirmation was successful. Note that this boolean does not indicate
78
- # the confirmation status of the block. If confirmed, your block should
79
- # appear in {Nanook::Node#confirmation_history} within a short amount of
80
- # time, or you can use the convenience method {Nanook::Block#confirmed_recently?}
103
+ # the confirmation status of the block.
81
104
  #
82
105
  # ==== Example:
83
106
  # block.confirm # => true
84
107
  #
85
108
  # @return [Boolean] if the confirmation request was sent successful
86
109
  def confirm
87
- rpc(:block_confirm, :hash)[:started] == 1
88
- end
89
-
90
- # This call is for internal diagnostics/debug purposes only. Do not
91
- # rely on this interface being stable and do not use in a production system.
92
- #
93
- # Check if the block appears in the list of recently confirmed blocks by
94
- # online representatives. The full list of blocks can be queried for with {Nanook::Node#confirmation_history}.
95
- #
96
- # This method can work in conjunction with {Nanook::Block#confirm},
97
- # whereby you can send any block (old or new) out to online representatives to
98
- # confirm. The confirmation process can take up to a couple of minutes.
99
- #
100
- # The method returning +false+ can indicate that the block is still in the process of being
101
- # confirmed and that you should call the method again soon, or that it
102
- # was confirmed earlier than the list available in {Nanook::Node#confirmation_history},
103
- # or that it was not confirmed.
104
- #
105
- # ==== Example:
106
- # block.confirmed_recently? # => true
107
- #
108
- # @return [Boolean] +true+ if the block has been recently confirmed by
109
- # online representatives.
110
- def confirmed_recently?
111
- @rpc.call(:confirmation_history)[:confirmations].map{|h| h[:hash]}.include?(@block)
110
+ rpc(:block_confirm, :hash, _access: :started) == 1
112
111
  end
113
- alias_method :recently_confirmed?, :confirmed_recently?
114
112
 
115
113
  # Generate work for a block.
116
114
  #
@@ -122,42 +120,7 @@ class Nanook
122
120
  # When +false+, the node will only generate work locally (default is +false+)
123
121
  # @return [String] the work id of the work completed.
124
122
  def generate_work(use_peers: false)
125
- rpc(:work_generate, :hash, use_peers: use_peers)[:work]
126
- end
127
-
128
- # Returns Array of Hashes containing information about a chain of
129
- # send/receive blocks, starting from this block.
130
- #
131
- # ==== Example:
132
- #
133
- # block.history(limit: 1)
134
- #
135
- # ==== Example response:
136
- #
137
- # [
138
- # {
139
- # :account=>"nano_3x7cjioqahgs5ppheys6prpqtb4rdknked83chf97bot1unrbdkaux37t31b",
140
- # :amount=>539834279601145558517940224,
141
- # :hash=>"36A0FB717368BA8CF8D255B63DC207771EABC6C6FFC22A7F455EC2209464897E",
142
- # :type=>"send"
143
- # }
144
- # ]
145
- #
146
- # @param limit [Integer] maximum number of send/receive block hashes
147
- # to return in the chain (default is 1000)
148
- def history(limit: 1000)
149
- rpc(:history, :hash, count: limit)[:history]
150
- end
151
-
152
- # Returns the block hash id.
153
- #
154
- # ==== Example:
155
- #
156
- # block.id #=> "FBF8B0E..."
157
- #
158
- # @return [String] the block hash id
159
- def id
160
- @block
123
+ rpc(:work_generate, :hash, use_peers: use_peers, _access: :work)
161
124
  end
162
125
 
163
126
  # Returns a Hash of information about the block.
@@ -170,65 +133,84 @@ class Nanook
170
133
  # ==== Example response:
171
134
  #
172
135
  # {
173
- # :id=>"36A0FB717368BA8CF8D255B63DC207771EABC6C6FFC22A7F455EC2209464897E",
174
- # :type=>"send",
175
- # :previous=>"FBF8B0E6623A31AB528EBD839EEAA91CAFD25C12294C46754E45FD017F7939EB",
176
- # :destination=>"nano_3x7cjioqahgs5ppheys6prpqtb4rdknked83chf97bot1unrbdkaux37t31b",
177
- # :balance=>"00000000000000000000000000000000",
178
- # :work=>"44cc24b60705083a",
179
- # :signature=>"42ADFEFE7C3FFF188AE92A202F8A5734DE91779C454613E446EEC93D001D6C953E9FD16730AF32C891791BA8EDAECEB059A213E2FE1EEB7ADF9D5D0815464D06"
136
+ # "account": Nanook::Account,
137
+ # "amount": 34.2,
138
+ # "balance": 2.3
139
+ # "height": 58,
140
+ # "local_timestamp": Time,
141
+ # "confirmed": true,
142
+ # "type": "send",
143
+ # "account": Nanook::Account,
144
+ # "previous": Nanook::Block,
145
+ # "representative": Nanook::Account,
146
+ # "link": Nanook::Block,
147
+ # "link_as_account": Nanook::Account,
148
+ # "signature": "82D41BC16F313E4B2243D14DFFA2FB04679C540C2095FEE7EAE0F2F26880AD56DD48D87A7CC5DD760C5B2D76EE2C205506AA557BF00B60D8DEE312EC7343A501",
149
+ # "work": "8a142e07a10996d5"
180
150
  # }
181
151
  #
182
152
  # @param allow_unchecked [Boolean] (default is +false+). If +true+,
183
153
  # information can be returned about blocks that are unchecked (unverified).
184
- def info(allow_unchecked: false)
185
- if allow_unchecked
186
- response = rpc(:unchecked_get, :hash)
187
- unless response.has_key?(:error)
188
- return _parse_info_response(response)
189
- end
190
- # If unchecked not found, continue to checked block
154
+ # @raise [Nanook::NanoUnitError] if `unit` is invalid
155
+ # @raise [Nanook::NodeRpcError] if block is not found on the node.
156
+ def info(allow_unchecked: false, unit: Nanook.default_unit)
157
+ validate_unit!(unit)
158
+
159
+ # Params for both `unchecked_get` and `block_info` calls
160
+ params = {
161
+ json_block: true,
162
+ _coerce: Hash
163
+ }
164
+
165
+ begin
166
+ response = rpc(:block_info, :hash, params)
167
+ response.merge!(confirmed: true)
168
+ rescue Nanook::NodeRpcError => e
169
+ raise e unless allow_unchecked
170
+
171
+ response = rpc(:unchecked_get, :hash, params)
172
+ response.merge!(confirmed: false)
191
173
  end
192
174
 
193
- response = rpc(:block, :hash)
194
- _parse_info_response(response)
175
+ parse_info_response(response, unit)
195
176
  end
196
177
 
197
178
  # ==== Example:
198
179
  #
199
- # block.is_valid_work?("2bf29ef00786a6bc") # => true
180
+ # block.valid_work?("2bf29ef00786a6bc") # => true
200
181
  #
201
182
  # @param work [String] the work id to check is valid
202
183
  # @return [Boolean] signalling if work is valid for the block
203
- def is_valid_work?(work)
184
+ def valid_work?(work)
204
185
  response = rpc(:work_validate, :hash, work: work)
205
- !response.empty? && response[:valid] == 1
186
+ response[:valid_all] == 1 || response[:valid_receive] == 1
206
187
  end
207
188
 
208
189
  # Republish blocks starting at this block up the account chain
209
190
  # back to the nano network.
210
191
  #
211
- # @return [Array<String>] block hashes that were republished
192
+ # @return [Array<Nanook::Block>] blocks that were republished
212
193
  #
213
194
  # ==== Example:
214
195
  #
215
- # block.republish
216
- #
217
- # ==== Example response:
218
- #
219
- # ["36A0FB717368BA8CF8D255B63DC207771EABC6C6FFC22A7F455EC2209464897E"]
220
- def republish(destinations:nil, sources:nil)
196
+ # block.republish # => [Nanook::Block, ...]
197
+ def republish(destinations: nil, sources: nil)
221
198
  if !destinations.nil? && !sources.nil?
222
- raise ArgumentError.new("You must provide either destinations or sources but not both")
199
+ raise ArgumentError, 'You must provide either destinations or sources but not both'
223
200
  end
224
201
 
225
- # Add in optional arguments
226
- params = {}
202
+ params = {
203
+ _access: :blocks,
204
+ _coerce: Array
205
+ }
206
+
227
207
  params[:destinations] = destinations unless destinations.nil?
228
208
  params[:sources] = sources unless sources.nil?
229
- params[:count] = 1 unless params.empty?
209
+ params[:count] = 1 if destinations || sources
230
210
 
231
- rpc(:republish, :hash, params)[:blocks]
211
+ rpc(:republish, :hash, params).map do |block|
212
+ as_block(block)
213
+ end
232
214
  end
233
215
 
234
216
  # ==== Example:
@@ -237,24 +219,9 @@ class Nanook
237
219
  #
238
220
  # @return [Boolean] signalling if the block is a pending block.
239
221
  def pending?
240
- response = rpc(:pending_exists, :hash)
241
- !response.empty? && response[:exists] == 1
222
+ rpc(:pending_exists, :hash, _access: :exists) == 1
242
223
  end
243
224
 
244
- # Publish the block to the nano network.
245
- #
246
- # Note, if block has previously been published, use #republish instead.
247
- #
248
- # ==== Examples:
249
- #
250
- # block.publish # => "FBF8B0E..."
251
- #
252
- # @return [String] the block hash, or false.
253
- def publish
254
- rpc(:process, :block)[:hash] || false
255
- end
256
- alias_method :process, :publish
257
-
258
225
  # Returns an Array of block hashes in the account chain ending at
259
226
  # this block.
260
227
  #
@@ -262,51 +229,277 @@ class Nanook
262
229
  #
263
230
  # ==== Example:
264
231
  #
265
- # block.successors
266
- #
267
- # ==== Example response:
268
- #
269
- # ["36A0FB717368BA8CF8D255B63DC207771EABC6C6FFC22A7F455EC2209464897E"]
232
+ # block.successors # => [Nanook::Block, .. ]
270
233
  #
271
234
  # @param limit [Integer] maximum number of send/receive block hashes
272
235
  # to return in the chain (default is 1000)
273
236
  # @param offset [Integer] return the account chain block hashes offset
274
237
  # by the specified number of blocks (default is 0)
275
- # @return [Array<String>] block hashes in the account chain ending at this block
238
+ # @return [Array<Nanook::Block>] blocks in the account chain ending at this block
276
239
  def successors(limit: 1000, offset: 0)
277
- response = rpc(:successors, :block, count: limit, offset: offset)[:blocks]
278
- Nanook::Util.coerce_empty_string_to_type(response, Array)
240
+ params = {
241
+ count: limit,
242
+ offset: offset,
243
+ _access: :blocks,
244
+ _coerce: Array
245
+ }
246
+
247
+ rpc(:successors, :block, params).map do |block|
248
+ as_block(block)
249
+ end
250
+ end
251
+
252
+ # Returns the {Nanook::Account} of the block representative.
253
+ #
254
+ # ==== Example:
255
+ # block.representative # => Nanook::Account
256
+ #
257
+ # @return [Nanook::Account] representative account of the block. Can be nil.
258
+ def representative
259
+ memoized_info[:representative]
260
+ end
261
+
262
+ # Returns the {Nanook::Account} of the block.
263
+ #
264
+ # ==== Example:
265
+ # block.account # => Nanook::Account
266
+ #
267
+ # @return [Nanook::Account] the account of the block. Can be nil.
268
+ def account
269
+ memoized_info[:account]
270
+ end
271
+
272
+ # Returns the amount of the block.
273
+ #
274
+ # ==== Example:
275
+ # block.amount # => 3.01
276
+ #
277
+ # @param unit (see Nanook::Account#balance)
278
+ # @raise [Nanook::NanoUnitError] if `unit` is invalid
279
+ # @return [Float]
280
+ def amount(unit: Nanook.default_unit)
281
+ validate_unit!(unit)
282
+
283
+ amount = memoized_info[:amount]
284
+ return amount unless unit == :nano
285
+
286
+ raw_to_NANO(amount)
287
+ end
288
+
289
+ # Returns the balance of the account at the time the block was created.
290
+ #
291
+ # ==== Example:
292
+ # block.balance # => 3.01
293
+ #
294
+ # @param unit (see Nanook::Account#balance)
295
+ # @raise [Nanook::NanoUnitError] if `unit` is invalid
296
+ # @return [Float]
297
+ def balance(unit: Nanook.default_unit)
298
+ validate_unit!(unit)
299
+
300
+ balance = memoized_info[:balance]
301
+ return balance unless unit == :nano
302
+
303
+ raw_to_NANO(balance)
304
+ end
305
+
306
+ # Returns true if block is confirmed.
307
+ #
308
+ # ==== Example:
309
+ # block.confirmed # => true
310
+ #
311
+ # @return [Boolean]
312
+ def confirmed?
313
+ memoized_info[:confirmed]
314
+ end
315
+ alias checked? confirmed?
316
+
317
+ # Returns true if block is unconfirmed.
318
+ #
319
+ # ==== Example:
320
+ # block.unconfirmed? # => true
321
+ #
322
+ # @return [Boolean]
323
+ def unconfirmed?
324
+ !confirmed?
325
+ end
326
+ alias unchecked? unconfirmed?
327
+
328
+ # Returns true if block exists in the node's ledger. This will return
329
+ # false for blocks that exist on the nano ledger but have not yet
330
+ # synchronized to the node.
331
+ #
332
+ # ==== Example:
333
+ #
334
+ # block.exists? # => false
335
+ # block.exists?(allow_unchecked: true) # => true
336
+ #
337
+ # @param allow_unchecked [Boolean] defaults to +false+
338
+ # @return [Boolean]
339
+ def exists?(allow_unchecked: false)
340
+ begin
341
+ allow_unchecked ? memoized_info : info
342
+ rescue Nanook::NodeRpcError
343
+ return false
344
+ end
345
+
346
+ true
347
+ end
348
+
349
+ # Returns the height of the block.
350
+ #
351
+ # ==== Example:
352
+ # block.height # => 5
353
+ #
354
+ # @return [Integer]
355
+ def height
356
+ memoized_info[:height]
357
+ end
358
+
359
+ # Returns the block work.
360
+ #
361
+ # ==== Example:
362
+ # block.work # => "8a142e07a10996d5"
363
+ #
364
+ # @return [String]
365
+ def work
366
+ memoized_info[:work]
279
367
  end
280
368
 
281
- def inspect
282
- "#{self.class.name}(id: \"#{id}\", object_id: \"#{"0x00%x" % (object_id << 1)}\")"
369
+ # Returns the block signature.
370
+ #
371
+ # ==== Example:
372
+ # block.signature # => "82D41BC16F313E4B2243D14DFFA2FB04679C540C2095FEE7EAE0F2F26880AD56DD48D87A7CC5DD760C5B2D76EE2C205506AA557BF00B60D8DEE312EC7343A501"
373
+ #
374
+ # @return [String]
375
+ def signature
376
+ memoized_info[:signature]
283
377
  end
284
378
 
379
+ # Returns the timestamp of when the node saw the block.
380
+ #
381
+ # ==== Example:
382
+ # block.timestamp # => 2018-05-30 16:41:48 UTC
383
+ #
384
+ # @return [Time] Time in UTC of when the node saw the block. Can be nil.
385
+ def timestamp
386
+ memoized_info[:local_timestamp]
387
+ end
388
+
389
+ # Returns the {Nanook::Block} of the previous block in the chain.
390
+ #
391
+ # ==== Example:
392
+ # block.previous # => Nanook::Block
393
+ #
394
+ # @return [Nanook::Block] previous block in the chain. Can be nil.
395
+ def previous
396
+ memoized_info[:previous]
397
+ end
398
+
399
+ # Returns the type of the block. One of "open", "send", "receive", "change", "epoch".
400
+ #
401
+ # ==== Example:
402
+ # block.type # => "open"
403
+ #
404
+ # @return [String] type of block. Returns nil for unconfirmed blocks.
405
+ def type
406
+ memoized_info[:type]
407
+ end
408
+
409
+ # Returns true if block is type "send".
410
+ #
411
+ # ==== Example:
412
+ # block.send? # => true
413
+ #
414
+ # @return [Boolean]
415
+ def send?
416
+ type == 'send'
417
+ end
418
+
419
+ # Returns true if block is type "open".
420
+ #
421
+ # ==== Example:
422
+ # block.open? # => true
423
+ #
424
+ # @return [Boolean]
425
+ def open?
426
+ type == 'open'
427
+ end
428
+
429
+ # Returns true if block is type "receive".
430
+ #
431
+ # ==== Example:
432
+ # block.receive? # => true
433
+ #
434
+ # @return [Boolean]
435
+ def receive?
436
+ type == 'receive'
437
+ end
438
+
439
+ # Returns true if block is type "change" (change of representative).
440
+ #
441
+ # ==== Example:
442
+ # block.change? # => true
443
+ #
444
+ # @return [Boolean]
445
+ def change?
446
+ type == 'change'
447
+ end
448
+
449
+ # Returns true if block is type "epoch".
450
+ #
451
+ # ==== Example:
452
+ # block.epoch? # => true
453
+ #
454
+ # @return [Boolean]
455
+ def epoch?
456
+ type == 'epoch'
457
+ end
458
+
459
+ # @return [String]
460
+ def to_s
461
+ "#{self.class.name}(id: \"#{short_id}\")"
462
+ end
463
+ alias inspect to_s
464
+
285
465
  private
286
466
 
287
467
  # Some RPC calls expect the param that represents the block to be named
288
468
  # "hash", and others "block".
289
469
  # The param_name argument allows us to specify which it should be for this call.
290
- def rpc(action, param_name, params={})
291
- p = @block.nil? ? {} : { param_name.to_sym => @block }
470
+ def rpc(action, param_name, params = {})
471
+ p = { param_name.to_sym => @block }
292
472
  @rpc.call(action, p.merge(params))
293
473
  end
294
474
 
295
- def block_required!
296
- if @block.nil?
297
- raise ArgumentError.new("Block must be present")
298
- end
475
+ # Memoize the `#info` response as we can refer to it for other methods (`type`, `#open?`, `#send?` etc.)
476
+ def memoized_info
477
+ @memoized_info ||= info(allow_unchecked: true, unit: :raw)
299
478
  end
300
479
 
301
- def _parse_info_response(response)
302
- # The contents is a stringified JSON
303
- if response[:contents]
304
- r = JSON.parse(response[:contents]).to_symbolized_hash
305
- return r.merge(id: id)
480
+ def parse_info_response(response, unit)
481
+ response.merge!(id: id)
482
+ contents = response.delete(:contents)
483
+ response.merge!(contents) if contents
484
+
485
+ response.delete(:block_account) # duplicate of contents.account
486
+ response[:type] = response.delete(:subtype) # rename key
487
+ response[:last_modified_at] = response.delete(:modified_timestamp) # rename key
488
+
489
+ response[:account] = as_account(response[:account]) if response[:account]
490
+ response[:representative] = as_account(response[:representative]) if response[:representative]
491
+ response[:previous] = as_block(response[:previous]) if response[:previous]
492
+ response[:link] = as_block(response[:link]) if response[:link]
493
+ response[:link_as_account] = as_account(response[:link_as_account]) if response[:link_as_account]
494
+ response[:local_timestamp] = as_time(response[:local_timestamp])
495
+ response[:last_modified_at] = as_time(response[:last_modified_at])
496
+
497
+ if unit == :nano
498
+ response[:amount] = raw_to_NANO(response[:amount])
499
+ response[:balance] = raw_to_NANO(response[:balance])
306
500
  end
307
501
 
308
- response
502
+ response.compact
309
503
  end
310
-
311
504
  end
312
505
  end