Class: BlockGiven::Connectors::Http

Inherits:
Base
  • Object
show all
Defined in:
lib/block_given/connectors/http.rb

Overview

Generic JSON-RPC over HTTP(S) transport with retries and exponential backoff.

BlockGiven::Connectors::Http.new(url: "http://127.0.0.1:8545")
BlockGiven::Connectors::Http.new # -> falls back to chain.rpc_urls.first

Direct Known Subclasses

Alchemy

Defined Under Namespace

Classes: Retry

Constant Summary collapse

RETRIABLE_STATUSES =
[408, 425, 429, 500, 502, 503, 504].freeze
RETRIABLE_RPC_CODES =

rate limited / internal error

[-32_005, -32_603, 429].freeze
RETRIABLE_EXCEPTIONS =
[Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNRESET, Errno::ECONNREFUSED,
Errno::EHOSTUNREACH, EOFError, SocketError, OpenSSL::SSL::SSLError].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#name

Constructor Details

#initialize(url: nil, headers: {}, timeout: 30, retries: 3, retry_delay: 0.5, logger: nil) ⇒ Http

Returns a new instance of Http.



21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/block_given/connectors/http.rb', line 21

def initialize(url: nil, headers: {}, timeout: 30, retries: 3, retry_delay: 0.5, logger: nil)
  super()
  @url = url
  user_agent = "block_given/#{BlockGiven::VERSION}"
  @headers = { "Content-Type" => "application/json", "User-Agent" => user_agent }.merge(headers)
  @timeout = timeout
  @retries = retries
  @retry_delay = retry_delay
  @logger = logger
  @id = 0
  @mutex = Mutex.new
end

Instance Attribute Details

#headersObject (readonly)

Returns the value of attribute headers.



19
20
21
# File 'lib/block_given/connectors/http.rb', line 19

def headers
  @headers
end

#retriesObject (readonly)

Returns the value of attribute retries.



19
20
21
# File 'lib/block_given/connectors/http.rb', line 19

def retries
  @retries
end

#retry_delayObject (readonly)

Returns the value of attribute retry_delay.



19
20
21
# File 'lib/block_given/connectors/http.rb', line 19

def retry_delay
  @retry_delay
end

#timeoutObject (readonly)

Returns the value of attribute timeout.



19
20
21
# File 'lib/block_given/connectors/http.rb', line 19

def timeout
  @timeout
end

#urlObject (readonly)

Returns the value of attribute url.



19
20
21
# File 'lib/block_given/connectors/http.rb', line 19

def url
  @url
end

Instance Method Details

#batch(calls, chain: nil) ⇒ Object

Raises:



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/block_given/connectors/http.rb', line 47

def batch(calls, chain: nil)
  return [] if calls.empty?

  payload = calls.map { |(method, params)| { jsonrpc: "2.0", id: next_id, method: method, params: params || [] } }
  body = with_retries("batch") { post(endpoint(chain), payload) }
  raise RpcError, "batch response is not an array: #{body.inspect}" unless body.is_a?(Array)

  by_id = body.to_h { |entry| [entry["id"], entry] }
  payload.map do |req|
    entry = by_id[req[:id]] || { "error" => { "message" => "missing response for id #{req[:id]}" } }
    if entry.key?("error")
      RpcError.from_payload(entry["error"], rpc_method: req[:method])
    else
      entry["result"]
    end
  end
end

#endpoint(chain) ⇒ Object



34
35
36
37
38
39
# File 'lib/block_given/connectors/http.rb', line 34

def endpoint(chain)
  return url if url

  chain&.rpc_urls&.first ||
    raise(ConfigurationError, "no RPC url: pass url: to the connector or use a chain with rpc_urls")
end

#inspectObject



65
# File 'lib/block_given/connectors/http.rb', line 65

def inspect = "#<#{self.class.name} url=#{url ? redact(url).inspect : 'chain default'}>"

#redact(endpoint) ⇒ Object

Endpoint as it may appear in logs and error messages. RPC URLs usually carry the API key in their path (Infura, QuickNode, ...), so only scheme and host are kept.



69
70
71
72
73
74
75
76
# File 'lib/block_given/connectors/http.rb', line 69

def redact(endpoint)
  uri = URI.parse(endpoint.to_s)
  host = uri.port && uri.port != uri.default_port ? "#{uri.host}:#{uri.port}" : uri.host
  path = uri.path.to_s.delete_prefix("/").empty? ? "" : "/…"
  "#{uri.scheme}://#{host}#{path}"
rescue URI::InvalidURIError
  "<invalid url>"
end

#request(method, params = [], chain: nil) ⇒ Object



41
42
43
44
45
# File 'lib/block_given/connectors/http.rb', line 41

def request(method, params = [], chain: nil)
  payload = { jsonrpc: "2.0", id: next_id, method: method, params: params }
  body = with_retries(method) { post(endpoint(chain), payload) }
  handle_single(body, method)
end