nanook 2.5.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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