class Radiator::Api

Radiator::Api allows you to call remote methods to interact with the STEEM blockchain. The `Api` class is a shortened name for `Radiator::CondenserApi`.

Examples:

api = Radiator::Api.new
response = api.get_dynamic_global_properties
virtual_supply = response.result.virtual_supply

… or …

api = Radiator::Api.new
virtual_supply = api.get_dynamic_global_properties do |prop|
  prop.virtual_supply
end

If you need access to the `error` property, they can be accessed as follows:

api = Radiator::Api.new
response = api.get_dynamic_global_properties
if response.result.nil?
  puts response.error
  exit
end

virtual_supply = response.result.virtual_supply

… or …

api = Radiator::Api.new
virtual_supply = api.get_dynamic_global_properties do |prop, error|
  if prop.nil?
    puts error
    exis
  end

  prop.virtual_supply
end

List of remote methods:

set_subscribe_callback
set_pending_transaction_callback
set_block_applied_callback
cancel_all_subscriptions
get_trending_tags
get_tags_used_by_author
get_post_discussions_by_payout
get_comment_discussions_by_payout
get_discussions_by_trending
get_discussions_by_trending30
get_discussions_by_created
get_discussions_by_active
get_discussions_by_cashout
get_discussions_by_payout
get_discussions_by_votes
get_discussions_by_children
get_discussions_by_hot
get_discussions_by_feed
get_discussions_by_blog
get_discussions_by_comments
get_discussions_by_promoted
get_block_header
get_block
get_ops_in_block
get_state
get_trending_categories
get_best_categories
get_active_categories
get_recent_categories
get_config
get_dynamic_global_properties
get_chain_properties
get_feed_history
get_current_median_history_price
get_witness_schedule
get_hardfork_version
get_next_scheduled_hardfork
get_accounts
get_account_references
lookup_account_names
lookup_accounts
get_account_count
get_conversion_requests
get_account_history
get_owner_history
get_recovery_request
get_escrow
get_withdraw_routes
get_account_bandwidth
get_savings_withdraw_from
get_savings_withdraw_to
get_order_book
get_open_orders
get_liquidity_queue
get_transaction_hex
get_transaction
get_required_signatures
get_potential_signatures
verify_authority
verify_account_authority
get_active_votes
get_account_votes
get_content
get_content_replies
get_discussions_by_author_before_date
get_replies_by_last_update
get_witnesses
get_witness_by_account
get_witnesses_by_vote
lookup_witness_accounts
get_witness_count
get_active_witnesses
get_miner_queue
get_reward_fund

These methods and their characteristics are copied directly from methods marked as `database_api` in `steem-js`:

raw.githubusercontent.com/steemit/steem-js/master/src/api/methods.js

@see steemit.github.io/steemit-docs/#accounts

Constants

DEFAULT_HIVE_FAILOVER_URLS
DEFAULT_HIVE_RESTFUL_URL
DEFAULT_HIVE_URL
DEFAULT_STEEM_FAILOVER_URLS
DEFAULT_STEEM_RESTFUL_URL
DEFAULT_STEEM_URL
HEALTH_URI

@private

POST_HEADERS

@private

Public Class Methods

default_failover_urls(chain) click to toggle source
# File lib/radiator/api.rb, line 192
def self.default_failover_urls(chain)
  case chain.to_sym
  when :steem, :hive
    begin
      _api = Radiator::Api.new(url: DEFAULT_HIVE_FAILOVER_URLS.sample, failover_urls: DEFAULT_HIVE_FAILOVER_URLS)
      
      default_failover_urls = _api.get_accounts(['fullnodeupdate']) do |accounts|
        fullnodeupdate = accounts.first
        metadata = (JSON[fullnodeupdate.json_metadata] rescue nil) || {}
        report = metadata.fetch('report', [])
        
        if report.any?
          report.map do |r|
            if chain.to_sym == :steem && !r.fetch('hive', false)
              r.fetch('node')
            elsif chain.to_sym == :hive && r.fetch('hive', false)
              r.fetch('node')
            end
          end.compact
        end
      end
    rescue => e
      puts e
    end
  else; raise ApiError, "Unsupported chain: #{chain}"
  end
  
  if !!default_failover_urls
    default_failover_urls
  else
    case chain.to_sym
    when :steem then DEFAULT_STEEM_FAILOVER_URLS
    when :hive then DEFAULT_HIVE_FAILOVER_URLS
    else; []
    end
  end
end
default_restful_url(chain) click to toggle source
# File lib/radiator/api.rb, line 185
def self.default_restful_url(chain)
  case chain.to_sym
  when :steem then DEFAULT_STEEM_RESTFUL_URL
  when :hive then DEFAULT_HIVE_RESTFUL_URL
  end
end
default_url(chain) click to toggle source
# File lib/radiator/api.rb, line 177
def self.default_url(chain)
  case chain.to_sym
  when :steem then DEFAULT_STEEM_URL
  when :hive then DEFAULT_HIVE_URL
  else; raise ApiError, "Unsupported chain: #{chain}"
  end
end
network_api(chain, api_name, options = {}) click to toggle source
# File lib/radiator/api.rb, line 230
def self.network_api(chain, api_name, options = {})
  api = case chain.to_sym
  when :steem then Steem::Api.clone(freeze: true) rescue Api.clone
  when :hive then Hive::Api.clone(freeze: true) rescue Api.clone
  else; raise ApiError, "Unsupported chain: #{chain}"
  end
  
  api.api_name = api_name
  api.new(options) rescue nil
end
new(options = {}) click to toggle source

Cretes a new instance of Radiator::Api.

Examples:

api = Radiator::Api.new(url: 'https://api.example.com')

@param options [::Hash] The attributes to initialize the Radiator::Api with. @option options [String] :url URL that points at a full node, like `api.steemit.com`. Default from DEFAULT_URL. @option options [::Array<String>] :failover_urls An array that contains one or more full nodes to fall back on. Default from DEFAULT_FAILOVER_URLS. @option options [Logger] :logger An instance of `Logger` to send debug messages to. @option options [Boolean] :recover_transactions_on_error Have Radiator try to recover transactions that are accepted but could not be confirmed due to an error like network timeout. Default: `true` @option options [Integer] :max_requests Maximum number of requests on a connection before it is considered expired and automatically closed. @option options [Integer] :pool_size Maximum number of connections allowed. @option options [Boolean] :reuse_ssl_sessions Reuse a previously opened SSL session for a new connection. There's a slight performance improvement by enabling this, but at the expense of reliability during long execution. Default false. @option options [Boolean] :persist Enable or disable Persistent HTTP. Using Persistent HTTP keeps the connection alive between API calls. Default: `true`

# File lib/radiator/api.rb, line 256
def initialize(options = {})
  @user = options[:user]
  @password = options[:password]
  @chain = (options[:chain] || 'hive').to_sym
  @url = options[:url] || Api::default_url(@chain)
  @restful_url = options[:restful_url] || Api::default_restful_url(@chain)
  @preferred_url = @url.dup
  @failover_urls = options[:failover_urls]
  @debug = !!options[:debug]
  @max_requests = options[:max_requests] || 30
  @ssl_verify_mode = options[:ssl_verify_mode] || OpenSSL::SSL::VERIFY_PEER
  @ssl_version = options[:ssl_version]

  @self_logger = false
  @logger = if options[:logger].nil?
    @self_logger = true
    Radiator.logger
  else
    options[:logger]
  end
  
  @self_hashie_logger = false
  @hashie_logger = if options[:hashie_logger].nil?
    @self_hashie_logger = true
    Logger.new(nil)
  else
    options[:hashie_logger]
  end
  
  if @failover_urls.nil?
    @failover_urls = Api::default_failover_urls(@chain) - [@url]
  end
  
  @failover_urls = [@failover_urls].flatten.compact
  @preferred_failover_urls = @failover_urls.dup
  
  unless @hashie_logger.respond_to? :warn
    @hashie_logger = Logger.new(@hashie_logger)
  end
  
  @recover_transactions_on_error = if options.keys.include? :recover_transactions_on_error
    options[:recover_transactions_on_error]
  else
    true
  end
  
  @persist_error_count = 0
  @persist = if options.keys.include? :persist
    options[:persist]
  else
    true
  end
  
  @reuse_ssl_sessions = if options.keys.include? :reuse_ssl_sessions
    options[:reuse_ssl_sessions]
  else
    true
  end
  
  @use_condenser_namespace = if options.keys.include? :use_condenser_namespace
    options[:use_condenser_namespace]
  else
    true
  end
  
  if defined? Net::HTTP::Persistent::DEFAULT_POOL_SIZE
    @pool_size = options[:pool_size] || Net::HTTP::Persistent::DEFAULT_POOL_SIZE
  end
  
  Hashie.logger = @hashie_logger
  @method_names = nil
  @uri = nil
  @http_id = nil
  @http_memo = {}
  @api_options = options.dup.merge(chain: @chain)
  @api = nil
  @block_api = nil
  @backoff_at = nil
  @jussi_supported = []
  @network_api = Api::network_api(@chain, api_name, url: @url)
end

Private Class Methods

apply_http_defaults(http, ssl_verify_mode) click to toggle source
# File lib/radiator/api.rb, line 659
def self.apply_http_defaults(http, ssl_verify_mode)
  http.read_timeout = 10
  http.open_timeout = 10
  http.verify_mode = ssl_verify_mode
  http.ssl_timeout = 30 if defined? http.ssl_timeout
  http
end
finalize(logger, hashie_logger) click to toggle source
# File lib/radiator/api.rb, line 985
def self.finalize(logger, hashie_logger)
  proc {
    if !!logger && defined?(logger.close) && !logger.closed?
      logger.close
    end
    
    if !!hashie_logger && defined?(hashie_logger.close) && !hashie_logger.closed?
      hashie_logger.close
    end
  }
end
methods(api_name) click to toggle source
# File lib/radiator/api.rb, line 652
def self.methods(api_name)
  @methods ||= {}
  @methods[api_name] ||= JSON[File.read methods_json_path].map do |e|
    e if e['api'].to_sym == api_name
  end.compact.freeze
end
methods_json_path() click to toggle source
# File lib/radiator/api.rb, line 648
def self.methods_json_path
  @methods_json_path ||= "#{File.dirname(__FILE__)}/methods.json"
end

Public Instance Methods

api_name() click to toggle source

@private

# File lib/radiator/api.rb, line 424
def api_name
  :condenser_api
end
get_blocks(block_number) { |get_block| ... } click to toggle source

Get a specific block or range of blocks.

Example:

api = Radiator::Api.new
blocks = api.get_blocks(10..20)
transactions = blocks.flat_map(&:transactions)

… or …

api = Radiator::Api.new
transactions = []
api.get_blocks(10..20) do |block|
  transactions += block.transactions
end

@param block_number [Fixnum || ::Array<Fixnum>] @param block the block to execute for each result, optional. @return [::Array]

# File lib/radiator/api.rb, line 357
def get_blocks(block_number, &block)
  block_number = [*(block_number)].flatten
  
  if !!block
    block_number.each do |i|
      if use_condenser_namespace?
        yield api.get_block(i)
      else
        yield block_api.get_block(block_num: i).result, i
      end
    end
  else
    block_number.map do |i|
      if use_condenser_namespace?
        api.get_block(i)
      else
        block_api.get_block(block_num: i).result
      end
    end
  end
end
inspect() click to toggle source
# File lib/radiator/api.rb, line 614
def inspect
  properties = %w(
    chain url backoff_at max_requests ssl_verify_mode ssl_version persist
    recover_transactions_on_error reuse_ssl_sessions pool_size
    use_condenser_namespace
  ).map do |prop|
    if !!(v = instance_variable_get("@#{prop}"))
      "@#{prop}=#{v}" 
    end
  end.compact.join(', ')
  
  "#<#{self.class.name} [#{properties}]>"
end
method_missing(m, *args) { |*r| ... } click to toggle source

@private

Calls superclass method
# File lib/radiator/api.rb, line 436
def method_missing(m, *args, &block)
  super unless respond_to_missing?(m)
  
  current_rpc_id = rpc_id
  method_name = [api_name, m].join('.')
  response = nil
  options = if api_name == :condenser_api
    {
      jsonrpc: "2.0",
      method: method_name,
      params: args,
      id: current_rpc_id,
    }
  else
    rpc_args = if args.empty?
      {}
    else
      args.first
    end
    
    {
      jsonrpc: "2.0",
      method: method_name,
      params: rpc_args,
      id: current_rpc_id,
    }
  end
  
  tries = 0
  timestamp = Time.now.utc
  
  loop do
    tries += 1
    
    if tries > 5 && flappy? && !check_file_open?
      raise ApiError, 'PANIC: Out of file resources'
    end
    
    begin
      if tries > 1 && @recover_transactions_on_error && api_name == :network_broadcast_api
        signatures, exp = extract_signatures(options)
        
        if !!signatures && signatures.any?
          offset = [(exp - timestamp).abs, 30].min
          
          if !!(response = recover_transaction(signatures, current_rpc_id, timestamp - offset))
            response = Hashie::Mash.new(response)
          end
        end
      end
      
      @network_api ||= Api::network_api(@chain, api_name, url: @uri)
      
      if !!@network_api && @network_api.respond_to?(m)
        if !!block
          @network_api.send(m, *args) do |*r|
            return yield(*r)
          end
        else
          return @network_api.send(m, *args)
        end
      end
      
      if response.nil?
        response = request(options)
        
        response = if response.nil?
          error "No response, retrying ...", method_name
        elsif !response.kind_of? Net::HTTPSuccess
          warning "Unexpected response (code: #{response.code}): #{response.inspect}, retrying ...", method_name, true
        else
          detect_jussi(response)
          
          case response.code
          when '200'
            body = response.body
            response = JSON[body]
            
            if response['id'] != options[:id]
              debug_payload(options, body) if ENV['DEBUG'] == 'true'
              
              if !!response['id']
                warning "Unexpected rpc_id (expected: #{options[:id]}, got: #{response['id']}), retrying ...", method_name, true
              else
                # The node has broken the jsonrpc spec.
                warning "Node did not provide jsonrpc id (expected: #{options[:id]}, got: nothing), retrying ...", method_name, true
              end
              
              if response.keys.include?('error')
                handle_error(response, options, method_name, tries)
              end
            elsif response.keys.include?('error')
              handle_error(response, options, method_name, tries)
            else
              Hashie::Mash.new(response)
            end
          when '400' then warning 'Code 400: Bad Request, retrying ...', method_name, true
          when '429' then warning 'Code 429: Too Many Requests, retrying ...', method_name, true
          when '502' then warning 'Code 502: Bad Gateway, retrying ...', method_name, true
          when '503' then warning 'Code 503: Service Unavailable, retrying ...', method_name, true
          when '504' then warning 'Code 504: Gateway Timeout, retrying ...', method_name, true
          else
            warning "Unknown code #{response.code}, retrying ...", method_name, true
            warning response
          end
        end
      end
    rescue Net::HTTP::Persistent::Error => e
      warning "Unable to perform request: #{e} :: #{!!e.cause ? "cause: #{e.cause.message}" : ''}, retrying ...", method_name, true
      if e.cause.class == Net::HTTPMethodNotAllowed
        warning 'Node upstream is misconfigured.'
        drop_current_failover_url method_name
      end
      
      @persist_error_count += 1
    rescue ConnectionPool::Error => e
      warning "Connection Pool Error (#{e.message}), retrying ...", method_name, true
    rescue Errno::ECONNREFUSED => e
      warning 'Connection refused, retrying ...', method_name, true
    rescue Errno::EADDRNOTAVAIL => e
      warning 'Node not available, retrying ...', method_name, true
    rescue Errno::ECONNRESET => e
      warning "Connection Reset (#{e.message}), retrying ...", method_name, true
    rescue Errno::EBUSY => e
      warning "Resource busy (#{e.message}), retrying ...", method_name, true
    rescue Errno::ENETDOWN => e
      warning "Network down (#{e.message}), retrying ...", method_name, true
    rescue Net::ReadTimeout => e
      warning 'Node read timeout, retrying ...', method_name, true
    rescue Net::OpenTimeout => e
      warning 'Node timeout, retrying ...', method_name, true
    rescue RangeError => e
      warning 'Range Error, retrying ...', method_name, true
    rescue OpenSSL::SSL::SSLError => e
      warning "SSL Error (#{e.message}), retrying ...", method_name, true
    rescue SocketError => e
      warning "Socket Error (#{e.message}), retrying ...", method_name, true
    rescue JSON::ParserError => e
      warning "JSON Parse Error (#{e.message}), retrying ...", method_name, true
      drop_current_failover_url method_name if tries > 5
      response = nil
    rescue ApiError => e
      warning "ApiError (#{e.message}), retrying ...", method_name, true
    # rescue => e
    #   warning "Unknown exception from request, retrying ...", method_name, true
    #   warning e
    end
    
    # failover latch
    @network_api = nil if !!@network_api
    
    if !!response
      @persist_error_count = 0
      
      if !!block
        if api_name == :condenser_api
          return yield(response.result, response.error, response.id)
        else
          if defined?(response.result.size) && response.result.size == 0
            return yield(nil, response.error, response.id)
          elsif (
            defined?(response.result.size) && response.result.size == 1 &&
            defined?(response.result.values)
          )
            return yield(response.result.values.first, response.error, response.id)
          else
            return yield(response.result, response.error, response.id)
          end
        end
      else
        return response
      end
    end

    backoff
  end # loop
end
method_names() click to toggle source

@private

# File lib/radiator/api.rb, line 414
def method_names
  return @method_names if !!@method_names
  return CondenserApi::METHOD_NAMES if api_name == :condenser_api

  @method_names = Radiator::Api.methods(api_name).map do |e|
    e['method'].to_sym
  end
end
respond_to_missing?(m, include_private = false) click to toggle source

@private

# File lib/radiator/api.rb, line 429
def respond_to_missing?(m, include_private = false)
  return true if @network_api.respond_to? m.to_sym
  
  method_names.nil? ? false : method_names.include?(m.to_sym)
end
shutdown() click to toggle source

Stops the persistant http connections.

# File lib/radiator/api.rb, line 381
def shutdown
  @uri = nil
  @http_id = nil
  @http_memo.each do |k|
    v = @http_memo.delete(k)
    if defined?(v.shutdown)
      debug "Shutting down instance #{k} (#{v})"
      v.shutdown
    end
  end
  @api.shutdown if !!@api && @api != self
  @api = nil
  @block_api.shutdown if !!@block_api && @block_api != self
  @block_api = nil
  
  if @self_logger
    if !!@logger && defined?(@logger.close)
      if defined?(@logger.closed?)
        @logger.close unless @logger.closed?
      end
    end
  end
  
  if @self_hashie_logger
    if !!@hashie_logger && defined?(@hashie_logger.close)
      if defined?(@hashie_logger.closed?)
        @hashie_logger.close unless @hashie_logger.closed?
      end
    end
  end
end
stopped?() click to toggle source
# File lib/radiator/api.rb, line 628
def stopped?
  http_active = if @http_memo.nil?
    false
  else
    @http_memo.values.map do |http|
      if defined?(http.active?)
        http.active?
      else
        false
      end
    end.include?(true)
  end
  
  @uri.nil? && @http_id.nil? && !http_active && @api.nil? && @block_api.nil?
end
use_condenser_namespace?() click to toggle source
# File lib/radiator/api.rb, line 644
def use_condenser_namespace?
  @use_condenser_namespace
end

Private Instance Methods

api() click to toggle source
# File lib/radiator/api.rb, line 671
def api
  @api ||= self.class == Api ? self : Api.new(api_options)
end
api_options() click to toggle source
# File lib/radiator/api.rb, line 667
def api_options
  @api_options.merge(failover_urls: @failover_urls, logger: @logger, hashie_logger: @hashie_logger)
end
backoff() click to toggle source
# File lib/radiator/api.rb, line 969
def backoff
  shutdown
  bump_failover if flappy? || !healthy?(uri)
  @backoff_at ||= Time.now.utc
  @backoff_sleep ||= 0.01
  
  @backoff_sleep *= 2
  GC.start
  sleep @backoff_sleep
ensure
  if !!@backoff_at && Time.now.utc - @backoff_at > 300
    @backoff_at = nil 
    @backoff_sleep = nil
  end
end
block_api() click to toggle source
# File lib/radiator/api.rb, line 675
def block_api
  @block_api ||= self.class == BlockApi ? self : BlockApi.new(api_options)
end
bump_failover() click to toggle source
# File lib/radiator/api.rb, line 828
def bump_failover
  @uri = nil
  @url = pop_failover_url
  warning "Failing over to #{@url} ..."
end
check_file_open?() click to toggle source
# File lib/radiator/api.rb, line 949
def check_file_open?
  File.exists?('.')
rescue
  false
end
debug_payload(request, response) click to toggle source
# File lib/radiator/api.rb, line 955
def debug_payload(request, response)
  request = JSON.pretty_generate(request)
  response = JSON.parse(response) rescue response
  response = JSON.pretty_generate(response) rescue response
  
  puts '=' * 80
  puts "Request:"
  puts request
  puts '=' * 80
  puts "Response:"
  puts response
  puts '=' * 80
end
detect_jussi(response) click to toggle source
# File lib/radiator/api.rb, line 740
def detect_jussi(response)
  return if jussi_supported?(@url)
  
  jussi_response_id = response['x-jussi-response-id']
  
  if !!jussi_response_id
    debug "Found a node that supports jussi: #{@url}"
    @jussi_supported << @url
  end
end
drop_current_failover_url(prefix) click to toggle source

Note, this methods only removes the uri.to_s if present but it does not call bump_failover, in order to avoid a race condition.

# File lib/radiator/api.rb, line 840
def drop_current_failover_url(prefix)
  if @preferred_failover_urls.size == 1
    warning "Node #{uri} appears to be misconfigured but no other node is available, retrying ...", prefix
  else
    warning "Removing misconfigured node from failover urls: #{uri}, retrying ...", prefix
    @preferred_failover_urls.delete(uri.to_s)
    @failover_urls.delete(uri.to_s)
  end
end
flappy?() click to toggle source
# File lib/radiator/api.rb, line 834
def flappy?
  !!@backoff_at && Time.now.utc - @backoff_at < 300
end
handle_error(response, request_options, method_name, tries) click to toggle source
# File lib/radiator/api.rb, line 850
def handle_error(response, request_options, method_name, tries)
  parser = ErrorParser.new(response)
  _signatures, exp = extract_signatures(request_options)
  
  if (!!exp && exp < Time.now.utc) || (tries > 2 && !parser.node_degraded?)
    # Whatever the error was, it is already expired or tried too much.  No
    # need to try to recover.
    
    debug "Error code #{parser} but transaction already expired or too many tries, giving up (attempt: #{tries})."
  elsif parser.can_retry?
    drop_current_failover_url method_name if !!exp && parser.expiry?
    drop_current_failover_url method_name if parser.node_degraded?
    debug "Error code #{parser} (attempt: #{tries}), retrying ..."
    return nil
  end
  
  if !!parser.trx_id
    # Turns out, the ErrorParser found a transaction id.  It might come in
    # handy, so let's append this to the result along with the error.
    
    response[:result] = {
      id: parser.trx_id,
      block_num: -1,
      trx_num: -1,
      expired: false
    }
    
    if @recover_transactions_on_error
      begin
        if !!@restful_url
          JSON[open("#{@restful_url}/account_history_api/get_transaction?id=#{parser.trx_id}").read].tap do |tx|
            response[:result][:block_num] = tx['block_num']
            response[:result][:trx_num] = tx['transaction_num']
          end
        else
          # Node operators often disable this operation.
          api.get_transaction(parser.trx_id) do |tx|
            if !!tx
              response[:result][:block_num] = tx.block_num
              response[:result][:trx_num] = tx.transaction_num
            end
          end
        end
        
        response[:recovered_by] = http_id
        response.delete('error') # no need for this, now
      rescue
        debug "Couldn't find block for trx_id: #{parser.trx_id}, giving up."
      end
    end
  end
  
  Hashie::Mash.new(response)
end
healthy?(url) click to toggle source
# File lib/radiator/api.rb, line 905
def healthy?(url)
  begin
    # Note, not all nodes support the /health uri.  But even if they don't,
    # they'll respond status code 200 OK, even if the body shows an error.
    
    # But if the node supports the /health uri, it will do additional
    # verifications on the block height.
    # See: https://github.com/steemit/steem/blob/master/contrib/healthcheck.sh
    
    # Also note, this check is done **without** net-http-persistent.
    
    response = open(url + HEALTH_URI)
    response = JSON[response.read]
    
    if !!response['error']
      if !!response['error']['data']
        if !!response['error']['data']['message']
          error "#{url} error: #{response['error']['data']['message']}"
        end
      elsif !!response['error']['message']
        error "#{url} error: #{response['error']['message']}"
      else
        error "#{url} error: #{response['error']}"
      end
      
      false
    elsif response['status'] == 'OK'
      true
    else
      error "#{url} status: #{response['status']}"
      
      false
    end
  rescue JSON::ParserError
    # No JSON, but also no HTTP error code, so we're OK.
    
    true
  rescue => e
    error "Health check failure for #{url}: #{e.inspect}"
    sleep 0.2
    false
  end
end
http() click to toggle source
# File lib/radiator/api.rb, line 692
def http
  return @http_memo[http_id] if @http_memo.keys.include? http_id
  
  @http_memo[http_id] = if @persist && @persist_error_count < 10
    idempotent = api_name != :network_broadcast_api
    
    http = if defined? Net::HTTP::Persistent::DEFAULT_POOL_SIZE
      Net::HTTP::Persistent.new(name: http_id, pool_size: @pool_size)
    else
      # net-http-persistent < 3.0
      Net::HTTP::Persistent.new(http_id)
    end
    
    http.keep_alive = 30
    http.idle_timeout = idempotent ? 10 : nil
    http.max_requests = @max_requests
    http.retry_change_requests = idempotent if defined? http.retry_change_requests
    http.reuse_ssl_sessions = @reuse_ssl_sessions
    
    http
  else
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme == 'https'
    http
  end
  
  Api::apply_http_defaults(@http_memo[http_id], @ssl_verify_mode)
end
http_id() click to toggle source
# File lib/radiator/api.rb, line 688
def http_id
  @http_id ||= "radiator-#{Radiator::VERSION}-#{api_name}-#{SecureRandom.uuid}"
end
jussi_supported?(url = @url) click to toggle source
# File lib/radiator/api.rb, line 736
def jussi_supported?(url = @url)
  @jussi_supported.include? url
end
pop_failover_url() click to toggle source
# File lib/radiator/api.rb, line 818
def pop_failover_url
  reset_failover if @failover_urls.none?
  
  until @failover_urls.none? || healthy?(url = @failover_urls.sample)
    @failover_urls.delete(url)
  end
  
  url || (uri || @url).to_s
end
post_request() click to toggle source
# File lib/radiator/api.rb, line 721
def post_request
  Net::HTTP::Post.new uri.request_uri, POST_HEADERS
end
recover_transaction(signatures, expected_rpc_id, after) click to toggle source
# File lib/radiator/api.rb, line 751
def recover_transaction(signatures, expected_rpc_id, after)
  debug "Looking for signatures: #{signatures.map{|s| s[0..5]}} since: #{after}"
  
  count = 0
  start = Time.now.utc
  block_range = api.get_dynamic_global_properties do |properties|
    high = properties.head_block_number
    low = high - 100
    [*(low..(high))].reverse
  end
  
  # It would be nice if Steemit, Inc. would add an API method like
  # `get_transaction`, call it `get_transaction_by_signature`, so we didn't
  # have to scan the latest blocks like this.  At most, we read 100 blocks
  # but we also give up once the block time is before the `after` argument.
  
  api.get_blocks(block_range) do |block, block_num|
    unless defined? block.transaction_ids
      error "Blockchain does not provide transaction ids in blocks, giving up."
      return nil
    end
    
    count += 1
    raise ApiError, "Race condition detected on remote node at: #{block_num}" if block.nil?
    
    # TODO Some blockchains (like Golos) do not have transaction_ids.  In
    # the future, it would be better to decode the operation and signature
    # into the transaction id.
    # See: https://github.com/steemit/steem/issues/187
    # See: https://github.com/GolosChain/golos/issues/281
    unless defined? block.transaction_ids
      @recover_transactions_on_error = false
      return
    end
    
    timestamp = Time.parse(block.timestamp + 'Z')
    break if timestamp < after
    
    block.transactions.each_with_index do |tx, index|
      next unless ((tx['signatures'] || []) & signatures).any?

      debug "Found transaction #{count} block(s) ago; took #{(Time.now.utc - start)} seconds to scan."
      
      return {
        id: expected_rpc_id,
        recovered_by: http_id,
        result: {
          id: block.transaction_ids[index],
          block_num: block_num,
          trx_num: index,
          expired: false
        }
      }
    end
  end
  
  debug "Could not find transaction in #{count} block(s); took #{(Time.now.utc - start)} seconds to scan."
  
  return nil
end
request(options) click to toggle source
# File lib/radiator/api.rb, line 725
def request(options)
  request = post_request
  request.body = JSON[options]
  
  case http
  when Net::HTTP::Persistent then http.request(uri, request)
  when Net::HTTP then http.request(request)
  else; raise ApiError, "Unsuppored scheme: #{http.inspect}"
  end
end
reset_failover() click to toggle source
# File lib/radiator/api.rb, line 812
def reset_failover
  @url = @preferred_url.dup
  @failover_urls = @preferred_failover_urls.dup
  warning "Failover reset, going back to #{@url} ..."
end
rpc_id() click to toggle source
# File lib/radiator/api.rb, line 679
def rpc_id
  @rpc_id ||= 0
  @rpc_id = @rpc_id + 1
end
uri() click to toggle source
# File lib/radiator/api.rb, line 684
def uri
  @uri ||= URI.parse(@url)
end