lib/nanook/block.rb in nanook-2.5.1 vs lib/nanook/block.rb in nanook-3.0.0
- old
+ new
@@ -1,7 +1,10 @@
-class Nanook
+# frozen_string_literal: true
+require_relative 'util'
+
+class Nanook
# The <tt>Nanook::Block</tt> class contains methods to discover
# publicly-available information about blocks on the nano network.
#
# A block is represented by a unique id like this:
#
@@ -15,27 +18,44 @@
# Or compose the longhand way like this:
#
# rpc_conn = Nanook::Rpc.new
# block = Nanook::Block.new(rpc_conn, "FBF8B0E...")
class Block
+ include Nanook::Util
def initialize(rpc, block)
@rpc = rpc
- @block = block
- block_required! # All methods expect a block
+ @block = block.to_s
end
- # Returns the {Nanook::Account} of the block.
+ # Returns the block hash id.
#
# ==== Example:
- # block.account # => Nanook::Account
#
- # @return [Nanook::Account] the account of the block
- def account
- Nanook::Account.new(@rpc, rpc(:block_account, :hash)[:account])
+ # block.id #=> "FBF8B0E..."
+ #
+ # @return [String] the block hash id
+ def id
+ @block
end
+ # @param other [Nanook::Block] block to compare
+ # @return [Boolean] true if blocks are equal
+ def ==(other)
+ other.class == self.class &&
+ other.id == id
+ end
+ alias eql? ==
+
+ # The hash value is used along with #eql? by the Hash class to determine if two objects
+ # reference the same hash key.
+ #
+ # @return [Integer]
+ def hash
+ id.hash
+ end
+
# Stop generating work for a block.
#
# ==== Example:
#
# block.cancel_work # => true
@@ -57,256 +77,429 @@
#
# block.chain(limit: 2)
#
# ==== Example reponse:
#
- # [
- # "36A0FB717368BA8CF8D255B63DC207771EABC6C6FFC22A7F455EC2209464897E",
- # "FBF8B0E6623A31AB528EBD839EEAA91CAFD25C12294C46754E45FD017F7939EB"
- # ]
+ # [Nanook::Block, ...]
#
# @param limit [Integer] maximum number of block hashes to return (default is 1000)
# @param offset [Integer] return the account chain block hashes offset by the specified number of blocks (default is 0)
def chain(limit: 1000, offset: 0)
- response = rpc(:chain, :block, count: limit, offset: offset)[:blocks]
- Nanook::Util.coerce_empty_string_to_type(response, Array)
+ params = {
+ count: limit,
+ offset: offset,
+ _access: :blocks,
+ _coerce: Array
+ }
+
+ rpc(:chain, :block, params).map do |block|
+ as_block(block)
+ end
end
- alias_method :ancestors, :chain
+ alias ancestors chain
# Request confirmation for a block from online representative nodes.
# Will return immediately with a boolean to indicate if the request for
# confirmation was successful. Note that this boolean does not indicate
- # the confirmation status of the block. If confirmed, your block should
- # appear in {Nanook::Node#confirmation_history} within a short amount of
- # time, or you can use the convenience method {Nanook::Block#confirmed_recently?}
+ # the confirmation status of the block.
#
# ==== Example:
# block.confirm # => true
#
# @return [Boolean] if the confirmation request was sent successful
def confirm
- rpc(:block_confirm, :hash)[:started] == 1
+ rpc(:block_confirm, :hash, _access: :started) == 1
end
- # This call is for internal diagnostics/debug purposes only. Do not
- # rely on this interface being stable and do not use in a production system.
- #
- # Check if the block appears in the list of recently confirmed blocks by
- # online representatives. The full list of blocks can be queried for with {Nanook::Node#confirmation_history}.
- #
- # This method can work in conjunction with {Nanook::Block#confirm},
- # whereby you can send any block (old or new) out to online representatives to
- # confirm. The confirmation process can take up to a couple of minutes.
- #
- # The method returning +false+ can indicate that the block is still in the process of being
- # confirmed and that you should call the method again soon, or that it
- # was confirmed earlier than the list available in {Nanook::Node#confirmation_history},
- # or that it was not confirmed.
- #
- # ==== Example:
- # block.confirmed_recently? # => true
- #
- # @return [Boolean] +true+ if the block has been recently confirmed by
- # online representatives.
- def confirmed_recently?
- @rpc.call(:confirmation_history)[:confirmations].map{|h| h[:hash]}.include?(@block)
- end
- alias_method :recently_confirmed?, :confirmed_recently?
-
# Generate work for a block.
#
# ==== Example:
# block.generate_work # => "2bf29ef00786a6bc"
#
# @param use_peers [Boolean] if set to +true+, then the node will query
# its work peers (if it has any, see {Nanook::WorkPeer#list}).
# When +false+, the node will only generate work locally (default is +false+)
# @return [String] the work id of the work completed.
def generate_work(use_peers: false)
- rpc(:work_generate, :hash, use_peers: use_peers)[:work]
+ rpc(:work_generate, :hash, use_peers: use_peers, _access: :work)
end
- # Returns Array of Hashes containing information about a chain of
- # send/receive blocks, starting from this block.
- #
- # ==== Example:
- #
- # block.history(limit: 1)
- #
- # ==== Example response:
- #
- # [
- # {
- # :account=>"nano_3x7cjioqahgs5ppheys6prpqtb4rdknked83chf97bot1unrbdkaux37t31b",
- # :amount=>539834279601145558517940224,
- # :hash=>"36A0FB717368BA8CF8D255B63DC207771EABC6C6FFC22A7F455EC2209464897E",
- # :type=>"send"
- # }
- # ]
- #
- # @param limit [Integer] maximum number of send/receive block hashes
- # to return in the chain (default is 1000)
- def history(limit: 1000)
- rpc(:history, :hash, count: limit)[:history]
- end
-
- # Returns the block hash id.
- #
- # ==== Example:
- #
- # block.id #=> "FBF8B0E..."
- #
- # @return [String] the block hash id
- def id
- @block
- end
-
# Returns a Hash of information about the block.
#
# ==== Examples:
#
# block.info
# block.info(allow_unchecked: true)
#
# ==== Example response:
#
# {
- # :id=>"36A0FB717368BA8CF8D255B63DC207771EABC6C6FFC22A7F455EC2209464897E",
- # :type=>"send",
- # :previous=>"FBF8B0E6623A31AB528EBD839EEAA91CAFD25C12294C46754E45FD017F7939EB",
- # :destination=>"nano_3x7cjioqahgs5ppheys6prpqtb4rdknked83chf97bot1unrbdkaux37t31b",
- # :balance=>"00000000000000000000000000000000",
- # :work=>"44cc24b60705083a",
- # :signature=>"42ADFEFE7C3FFF188AE92A202F8A5734DE91779C454613E446EEC93D001D6C953E9FD16730AF32C891791BA8EDAECEB059A213E2FE1EEB7ADF9D5D0815464D06"
+ # "account": Nanook::Account,
+ # "amount": 34.2,
+ # "balance": 2.3
+ # "height": 58,
+ # "local_timestamp": Time,
+ # "confirmed": true,
+ # "type": "send",
+ # "account": Nanook::Account,
+ # "previous": Nanook::Block,
+ # "representative": Nanook::Account,
+ # "link": Nanook::Block,
+ # "link_as_account": Nanook::Account,
+ # "signature": "82D41BC16F313E4B2243D14DFFA2FB04679C540C2095FEE7EAE0F2F26880AD56DD48D87A7CC5DD760C5B2D76EE2C205506AA557BF00B60D8DEE312EC7343A501",
+ # "work": "8a142e07a10996d5"
# }
#
# @param allow_unchecked [Boolean] (default is +false+). If +true+,
# information can be returned about blocks that are unchecked (unverified).
- def info(allow_unchecked: false)
- if allow_unchecked
- response = rpc(:unchecked_get, :hash)
- unless response.has_key?(:error)
- return _parse_info_response(response)
- end
- # If unchecked not found, continue to checked block
+ # @raise [Nanook::NanoUnitError] if `unit` is invalid
+ # @raise [Nanook::NodeRpcError] if block is not found on the node.
+ def info(allow_unchecked: false, unit: Nanook.default_unit)
+ validate_unit!(unit)
+
+ # Params for both `unchecked_get` and `block_info` calls
+ params = {
+ json_block: true,
+ _coerce: Hash
+ }
+
+ begin
+ response = rpc(:block_info, :hash, params)
+ response.merge!(confirmed: true)
+ rescue Nanook::NodeRpcError => e
+ raise e unless allow_unchecked
+
+ response = rpc(:unchecked_get, :hash, params)
+ response.merge!(confirmed: false)
end
- response = rpc(:block, :hash)
- _parse_info_response(response)
+ parse_info_response(response, unit)
end
# ==== Example:
#
- # block.is_valid_work?("2bf29ef00786a6bc") # => true
+ # block.valid_work?("2bf29ef00786a6bc") # => true
#
# @param work [String] the work id to check is valid
# @return [Boolean] signalling if work is valid for the block
- def is_valid_work?(work)
+ def valid_work?(work)
response = rpc(:work_validate, :hash, work: work)
- !response.empty? && response[:valid] == 1
+ response[:valid_all] == 1 || response[:valid_receive] == 1
end
# Republish blocks starting at this block up the account chain
# back to the nano network.
#
- # @return [Array<String>] block hashes that were republished
+ # @return [Array<Nanook::Block>] blocks that were republished
#
# ==== Example:
#
- # block.republish
- #
- # ==== Example response:
- #
- # ["36A0FB717368BA8CF8D255B63DC207771EABC6C6FFC22A7F455EC2209464897E"]
- def republish(destinations:nil, sources:nil)
+ # block.republish # => [Nanook::Block, ...]
+ def republish(destinations: nil, sources: nil)
if !destinations.nil? && !sources.nil?
- raise ArgumentError.new("You must provide either destinations or sources but not both")
+ raise ArgumentError, 'You must provide either destinations or sources but not both'
end
- # Add in optional arguments
- params = {}
+ params = {
+ _access: :blocks,
+ _coerce: Array
+ }
+
params[:destinations] = destinations unless destinations.nil?
params[:sources] = sources unless sources.nil?
- params[:count] = 1 unless params.empty?
+ params[:count] = 1 if destinations || sources
- rpc(:republish, :hash, params)[:blocks]
+ rpc(:republish, :hash, params).map do |block|
+ as_block(block)
+ end
end
# ==== Example:
#
# block.pending? #=> false
#
# @return [Boolean] signalling if the block is a pending block.
def pending?
- response = rpc(:pending_exists, :hash)
- !response.empty? && response[:exists] == 1
+ rpc(:pending_exists, :hash, _access: :exists) == 1
end
- # Publish the block to the nano network.
- #
- # Note, if block has previously been published, use #republish instead.
- #
- # ==== Examples:
- #
- # block.publish # => "FBF8B0E..."
- #
- # @return [String] the block hash, or false.
- def publish
- rpc(:process, :block)[:hash] || false
- end
- alias_method :process, :publish
-
# Returns an Array of block hashes in the account chain ending at
# this block.
#
# See also #chain.
#
# ==== Example:
#
- # block.successors
+ # block.successors # => [Nanook::Block, .. ]
#
- # ==== Example response:
- #
- # ["36A0FB717368BA8CF8D255B63DC207771EABC6C6FFC22A7F455EC2209464897E"]
- #
# @param limit [Integer] maximum number of send/receive block hashes
# to return in the chain (default is 1000)
# @param offset [Integer] return the account chain block hashes offset
# by the specified number of blocks (default is 0)
- # @return [Array<String>] block hashes in the account chain ending at this block
+ # @return [Array<Nanook::Block>] blocks in the account chain ending at this block
def successors(limit: 1000, offset: 0)
- response = rpc(:successors, :block, count: limit, offset: offset)[:blocks]
- Nanook::Util.coerce_empty_string_to_type(response, Array)
+ params = {
+ count: limit,
+ offset: offset,
+ _access: :blocks,
+ _coerce: Array
+ }
+
+ rpc(:successors, :block, params).map do |block|
+ as_block(block)
+ end
end
- def inspect
- "#{self.class.name}(id: \"#{id}\", object_id: \"#{"0x00%x" % (object_id << 1)}\")"
+ # Returns the {Nanook::Account} of the block representative.
+ #
+ # ==== Example:
+ # block.representative # => Nanook::Account
+ #
+ # @return [Nanook::Account] representative account of the block. Can be nil.
+ def representative
+ memoized_info[:representative]
end
+ # Returns the {Nanook::Account} of the block.
+ #
+ # ==== Example:
+ # block.account # => Nanook::Account
+ #
+ # @return [Nanook::Account] the account of the block. Can be nil.
+ def account
+ memoized_info[:account]
+ end
+
+ # Returns the amount of the block.
+ #
+ # ==== Example:
+ # block.amount # => 3.01
+ #
+ # @param unit (see Nanook::Account#balance)
+ # @raise [Nanook::NanoUnitError] if `unit` is invalid
+ # @return [Float]
+ def amount(unit: Nanook.default_unit)
+ validate_unit!(unit)
+
+ amount = memoized_info[:amount]
+ return amount unless unit == :nano
+
+ raw_to_NANO(amount)
+ end
+
+ # Returns the balance of the account at the time the block was created.
+ #
+ # ==== Example:
+ # block.balance # => 3.01
+ #
+ # @param unit (see Nanook::Account#balance)
+ # @raise [Nanook::NanoUnitError] if `unit` is invalid
+ # @return [Float]
+ def balance(unit: Nanook.default_unit)
+ validate_unit!(unit)
+
+ balance = memoized_info[:balance]
+ return balance unless unit == :nano
+
+ raw_to_NANO(balance)
+ end
+
+ # Returns true if block is confirmed.
+ #
+ # ==== Example:
+ # block.confirmed # => true
+ #
+ # @return [Boolean]
+ def confirmed?
+ memoized_info[:confirmed]
+ end
+ alias checked? confirmed?
+
+ # Returns true if block is unconfirmed.
+ #
+ # ==== Example:
+ # block.unconfirmed? # => true
+ #
+ # @return [Boolean]
+ def unconfirmed?
+ !confirmed?
+ end
+ alias unchecked? unconfirmed?
+
+ # Returns true if block exists in the node's ledger. This will return
+ # false for blocks that exist on the nano ledger but have not yet
+ # synchronized to the node.
+ #
+ # ==== Example:
+ #
+ # block.exists? # => false
+ # block.exists?(allow_unchecked: true) # => true
+ #
+ # @param allow_unchecked [Boolean] defaults to +false+
+ # @return [Boolean]
+ def exists?(allow_unchecked: false)
+ begin
+ allow_unchecked ? memoized_info : info
+ rescue Nanook::NodeRpcError
+ return false
+ end
+
+ true
+ end
+
+ # Returns the height of the block.
+ #
+ # ==== Example:
+ # block.height # => 5
+ #
+ # @return [Integer]
+ def height
+ memoized_info[:height]
+ end
+
+ # Returns the block work.
+ #
+ # ==== Example:
+ # block.work # => "8a142e07a10996d5"
+ #
+ # @return [String]
+ def work
+ memoized_info[:work]
+ end
+
+ # Returns the block signature.
+ #
+ # ==== Example:
+ # block.signature # => "82D41BC16F313E4B2243D14DFFA2FB04679C540C2095FEE7EAE0F2F26880AD56DD48D87A7CC5DD760C5B2D76EE2C205506AA557BF00B60D8DEE312EC7343A501"
+ #
+ # @return [String]
+ def signature
+ memoized_info[:signature]
+ end
+
+ # Returns the timestamp of when the node saw the block.
+ #
+ # ==== Example:
+ # block.timestamp # => 2018-05-30 16:41:48 UTC
+ #
+ # @return [Time] Time in UTC of when the node saw the block. Can be nil.
+ def timestamp
+ memoized_info[:local_timestamp]
+ end
+
+ # Returns the {Nanook::Block} of the previous block in the chain.
+ #
+ # ==== Example:
+ # block.previous # => Nanook::Block
+ #
+ # @return [Nanook::Block] previous block in the chain. Can be nil.
+ def previous
+ memoized_info[:previous]
+ end
+
+ # Returns the type of the block. One of "open", "send", "receive", "change", "epoch".
+ #
+ # ==== Example:
+ # block.type # => "open"
+ #
+ # @return [String] type of block. Returns nil for unconfirmed blocks.
+ def type
+ memoized_info[:type]
+ end
+
+ # Returns true if block is type "send".
+ #
+ # ==== Example:
+ # block.send? # => true
+ #
+ # @return [Boolean]
+ def send?
+ type == 'send'
+ end
+
+ # Returns true if block is type "open".
+ #
+ # ==== Example:
+ # block.open? # => true
+ #
+ # @return [Boolean]
+ def open?
+ type == 'open'
+ end
+
+ # Returns true if block is type "receive".
+ #
+ # ==== Example:
+ # block.receive? # => true
+ #
+ # @return [Boolean]
+ def receive?
+ type == 'receive'
+ end
+
+ # Returns true if block is type "change" (change of representative).
+ #
+ # ==== Example:
+ # block.change? # => true
+ #
+ # @return [Boolean]
+ def change?
+ type == 'change'
+ end
+
+ # Returns true if block is type "epoch".
+ #
+ # ==== Example:
+ # block.epoch? # => true
+ #
+ # @return [Boolean]
+ def epoch?
+ type == 'epoch'
+ end
+
+ # @return [String]
+ def to_s
+ "#{self.class.name}(id: \"#{short_id}\")"
+ end
+ alias inspect to_s
+
private
# Some RPC calls expect the param that represents the block to be named
# "hash", and others "block".
# The param_name argument allows us to specify which it should be for this call.
- def rpc(action, param_name, params={})
- p = @block.nil? ? {} : { param_name.to_sym => @block }
+ def rpc(action, param_name, params = {})
+ p = { param_name.to_sym => @block }
@rpc.call(action, p.merge(params))
end
- def block_required!
- if @block.nil?
- raise ArgumentError.new("Block must be present")
- end
+ # Memoize the `#info` response as we can refer to it for other methods (`type`, `#open?`, `#send?` etc.)
+ def memoized_info
+ @memoized_info ||= info(allow_unchecked: true, unit: :raw)
end
- def _parse_info_response(response)
- # The contents is a stringified JSON
- if response[:contents]
- r = JSON.parse(response[:contents]).to_symbolized_hash
- return r.merge(id: id)
+ def parse_info_response(response, unit)
+ response.merge!(id: id)
+ contents = response.delete(:contents)
+ response.merge!(contents) if contents
+
+ response.delete(:block_account) # duplicate of contents.account
+ response[:type] = response.delete(:subtype) # rename key
+ response[:last_modified_at] = response.delete(:modified_timestamp) # rename key
+
+ response[:account] = as_account(response[:account]) if response[:account]
+ response[:representative] = as_account(response[:representative]) if response[:representative]
+ response[:previous] = as_block(response[:previous]) if response[:previous]
+ response[:link] = as_block(response[:link]) if response[:link]
+ response[:link_as_account] = as_account(response[:link_as_account]) if response[:link_as_account]
+ response[:local_timestamp] = as_time(response[:local_timestamp])
+ response[:last_modified_at] = as_time(response[:last_modified_at])
+
+ if unit == :nano
+ response[:amount] = raw_to_NANO(response[:amount])
+ response[:balance] = raw_to_NANO(response[:balance])
end
- response
+ response.compact
end
-
end
end