throttled_json_rpc_client 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 508629fc2c697fd81f0456ef75a786e1f25dcf2b75c83e7b0a43e520f8f744d4
4
- data.tar.gz: 322e0e8a61d8e210c89fd1f36c069d956e03a7e93f329896e7791c58cef08c53
3
+ metadata.gz: 59ebf0f85dc101410fcd0a3a665777ed1ffbae6e662e895acf7e338756cb8155
4
+ data.tar.gz: 58cee4e703c32b3a771b90bb5adc801f96d22b4359ff730ecac9820e8afaab91
5
5
  SHA512:
6
- metadata.gz: 4992ab8bad7c830d766edc3e65e83716e9723b563f49084bcbfc4ada67fe9dab3772f0daf188a5c5564daedcd2a17d5e886c54048d6dcda0b4928bb80929c790
7
- data.tar.gz: 72434e5bdc61bba92536af24d1475722b807e7f4c1acfd36edbad43921368d3127859f270ee8265a5026d101246f138cad1920f21eefdfa617927d019b86413c
6
+ metadata.gz: f6491a2d5685dae9e50c264271b6c07cb8701cb87c5b65709dfbcc4f594e3be7059deab89cc450a61402a32f7bd1a488dbd6f777bb189066ddc55d2600144a98
7
+ data.tar.gz: 237c09309c9f13de9b72660af01b8266ebc1f78deb9f31de11c71a7fba263e57611a503c19910091c5333e90bf11ee76f149f92020d4ed0f5509506dda88b2fb
data/README.md CHANGED
@@ -14,29 +14,18 @@ If bundler is not being used to manage dependencies, install the gem by executin
14
14
 
15
15
  ## Usage
16
16
 
17
- ## example1, without rate throttling
18
- ```ruby
19
- rpc_url = "https://1rpc.io/eth"
20
-
21
- eth = JsonRpcClient::Eth.new(rpc_url)
22
-
23
- p eth.block_number
24
- ```
25
-
26
- ## example2, with rate throttling
27
- > The rate limit on the server is called Rate Limiting, and the rate limit on the client is called Rate Throttling.
28
-
29
17
  This rate throttling can be used in multi-threaded, multi-process, multi-machine environments.
18
+
30
19
  ```ruby
31
- rpc_url = "https://1rpc.io/eth"
20
+ rpc_url = "https://eth.llamarpc.com"
32
21
 
33
22
  # default equals to ThrottledJsonRpcClient::Eth.new(url, rate: 5, interval: 1, redis_urls: ["redis://localhost:6379/2"])
34
- eth = ThrottledJsonRpcClient::Eth.new(rpc_url)
23
+ client = ThrottledJsonRpcClient::Client.new(rpc_url)
35
24
 
36
25
  threads = []
37
26
  10.times do
38
27
  threads << Thread.new do
39
- p eth.block_number
28
+ p client.call("eth_getBlockByNumber", ["0x1234", false])
40
29
  end
41
30
  end
42
31
  threads.map(&:join)
data/example.rb CHANGED
@@ -1,17 +1,17 @@
1
- require "logger"
1
+ rpc_url = "https://eth.llamarpc.com"
2
2
 
3
- rpc_url = "https://1rpc.io/eth"
4
-
5
- eth = ThrottledJsonRpcClient::Eth.new(
3
+ # limit: 1 request / 5 seconds
4
+ client = ThrottledJsonRpcClient::Client.new(
6
5
  rpc_url,
7
- redis_urls: ["redis://redis:6379/2"],
8
- logger: Logger.new($stdout, level: :debug)
6
+ rate: 1,
7
+ interval: 5,
8
+ redis_urls: ["redis://localhost:6379/2"]
9
9
  )
10
10
 
11
11
  threads = []
12
12
  10.times do
13
13
  threads << Thread.new do
14
- p eth.block_number
14
+ p client.call("eth_blockNumber", [])
15
15
  end
16
16
  end
17
17
  threads.map(&:join)
@@ -0,0 +1,97 @@
1
+ require "faraday"
2
+ require "json"
3
+ require "time"
4
+
5
+ class JsonRpcClient
6
+ class RpcError < StandardError; end
7
+
8
+ DEFAULT_MAX_BATCH_SIZE = 100
9
+ DEFAULT_TIMEOUT = 30
10
+
11
+ def initialize(
12
+ endpoint,
13
+ timeout: DEFAULT_TIMEOUT,
14
+ max_batch_size: DEFAULT_MAX_BATCH_SIZE,
15
+ headers: {}
16
+ )
17
+ @endpoint = endpoint
18
+ @max_batch_size = max_batch_size
19
+ @timeout = timeout
20
+ @headers = { "Content-Type" => "application/json" }.merge(headers)
21
+ @request_id = 0
22
+ @last_request_time = Time.now
23
+ @request_times = []
24
+ end
25
+
26
+ def call(method, params = nil)
27
+ payload = build_request(method, params)
28
+ response = make_request(payload)
29
+
30
+ raise RpcError, "RPC error: #{response["error"]}" if response["error"]
31
+
32
+ response["result"]
33
+ end
34
+
35
+ def batch_call(calls)
36
+ if calls.size > @max_batch_size
37
+ raise RpcError,
38
+ "Batch size #{calls.size} exceeds maximum allowed size of #{@max_batch_size}"
39
+ end
40
+
41
+ results = []
42
+ batch_payload = calls.map do |method, params|
43
+ build_request(method, params)
44
+ end
45
+
46
+ responses = make_request(batch_payload)
47
+
48
+ # Handle single response case
49
+ responses = [responses] unless responses.is_a?(Array)
50
+
51
+ # Sort responses by id to maintain order
52
+ sorted_responses = responses.sort_by { |r| r["id"] }
53
+
54
+ sorted_responses.each do |response|
55
+ raise RpcError, "RPC error in batch: #{response["error"]}" if response["error"]
56
+
57
+ results << response["result"]
58
+ end
59
+
60
+ results
61
+ end
62
+
63
+ private
64
+
65
+ def build_request(method, params)
66
+ @request_id += 1
67
+ {
68
+ jsonrpc: "2.0",
69
+ method: method,
70
+ params: params || [],
71
+ id: @request_id
72
+ }
73
+ end
74
+
75
+ def make_request(payload)
76
+ connection = Faraday.new(@endpoint) do |conn|
77
+ conn.options.timeout = @timeout
78
+ conn.ssl.verify = false
79
+ conn.adapter Faraday.default_adapter
80
+ end
81
+
82
+ response = connection.post do |req|
83
+ req.headers = @headers
84
+ req.body = payload.to_json
85
+ end
86
+
87
+ raise RpcError, "HTTP error: #{response.status} - #{response.body}" unless response.success?
88
+
89
+ JSON.parse(response.body)
90
+ rescue Faraday::TimeoutError => e
91
+ raise RpcError, "Request timed out after #{@timeout} seconds"
92
+ rescue Faraday::ConnectionFailed => e
93
+ raise RpcError, "Connection failed: #{e.message}"
94
+ rescue Faraday::SSLError => e
95
+ raise RpcError, "SSL Error: #{e.message}"
96
+ end
97
+ end
@@ -25,6 +25,7 @@ class DistributedRateQueue
25
25
 
26
26
  def wait_for_next_turn
27
27
  wait = lock_manager.get_remaining_ttl_for_resource(key)
28
+ return if wait.nil?
28
29
  return unless wait.positive?
29
30
 
30
31
  # Logger.log("Waiting for #{wait / 1000.0} seconds", Process.pid)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ThrottledJsonRpcClient
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -1,11 +1,79 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "net/http"
4
- require "json"
5
- require "uri"
6
-
3
+ require "delegate"
4
+ require_relative "limiter/distributed_rate_queue"
5
+ require_relative "json_rpc_client"
7
6
  require_relative "throttled_json_rpc_client/version"
8
7
 
9
- require_relative "limiter/distributed_rate_queue"
10
- require_relative "json_rpc_client/eth"
11
- require_relative "throttled_json_rpc_client/eth"
8
+ module ThrottledJsonRpcClient
9
+ class Client < SimpleDelegator
10
+ DEFAULT_MAX_RETRIES = 3
11
+ DEFAULT_BASE_DELAY = 1 # Base delay in seconds
12
+
13
+ # limit: #{rate} requests / #{interval} seconds
14
+ def initialize(
15
+ endpoint,
16
+ timeout: JsonRpcClient::DEFAULT_TIMEOUT,
17
+ max_batch_size: JsonRpcClient::DEFAULT_MAX_BATCH_SIZE,
18
+ headers: {},
19
+ # retry params
20
+ max_retries: DEFAULT_MAX_RETRIES,
21
+ base_delay: DEFAULT_BASE_DELAY,
22
+ # limit params
23
+ rate: 5,
24
+ interval: 1, # in seconds
25
+ redis_urls: ["redis://localhost:6379/2"]
26
+ )
27
+ @max_retries = max_retries
28
+ @base_delay = base_delay
29
+ @queue = DistributedRateQueue.new(
30
+ redis_urls: redis_urls,
31
+ key: "key:#{endpoint}",
32
+ rate: rate,
33
+ interval: interval
34
+ )
35
+ super(
36
+ JsonRpcClient.new(
37
+ endpoint,
38
+ timeout: timeout,
39
+ max_batch_size: max_batch_size,
40
+ headers: headers
41
+ )
42
+ )
43
+ end
44
+
45
+ def call(method, params = nil)
46
+ retries = 0
47
+ begin
48
+ @queue.shift do
49
+ super(method, params)
50
+ end
51
+ rescue JsonRpcClient::RpcError => e
52
+ retries += 1
53
+ if retries <= @max_retries
54
+ delay = @base_delay * (2**(retries - 1))
55
+ sleep(delay)
56
+ retry
57
+ end
58
+ raise e
59
+ end
60
+ end
61
+
62
+ def batch_call(calls)
63
+ retries = 0
64
+ begin
65
+ @queue.shift do
66
+ super(calls)
67
+ end
68
+ rescue JsonRpcClient::RpcError => e
69
+ retries += 1
70
+ if retries <= @max_retries
71
+ delay = @base_delay * (2**(retries - 1))
72
+ sleep(delay)
73
+ retry
74
+ end
75
+ raise e
76
+ end
77
+ end
78
+ end
79
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: throttled_json_rpc_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aki Wu
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-23 00:00:00.000000000 Z
11
+ date: 2024-11-25 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: redlock
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -38,10 +52,9 @@ files:
38
52
  - README.md
39
53
  - Rakefile
40
54
  - example.rb
41
- - lib/json_rpc_client/eth.rb
55
+ - lib/json_rpc_client.rb
42
56
  - lib/limiter/distributed_rate_queue.rb
43
57
  - lib/throttled_json_rpc_client.rb
44
- - lib/throttled_json_rpc_client/eth.rb
45
58
  - lib/throttled_json_rpc_client/version.rb
46
59
  - sig/throttled_json_rpc_client.rbs
47
60
  homepage: https://github.com/wuminzhe/throttled_json_rpc_client
@@ -66,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
79
  - !ruby/object:Gem::Version
67
80
  version: '0'
68
81
  requirements: []
69
- rubygems_version: 3.3.7
82
+ rubygems_version: 3.5.21
70
83
  signing_key:
71
84
  specification_version: 4
72
85
  summary: throttled_json_rpc_client
@@ -1,69 +0,0 @@
1
- module JsonRpcClient
2
- class HttpError < StandardError; end
3
- class JSONRpcError < StandardError; end
4
-
5
- class << self
6
- def request(
7
- url, method, params,
8
- logger = Logger.new($stdout, level: :info)
9
- )
10
- logger.debug "rpc url: #{url}"
11
-
12
- uri = URI.parse(url)
13
- http = Net::HTTP.new(uri.host, uri.port)
14
- http.use_ssl = uri.scheme == "https"
15
-
16
- request = Net::HTTP::Post.new(uri, "Content-Type" => "application/json")
17
- request.body = { jsonrpc: "2.0", method: method, params: params, id: Time.now.to_i }.to_json
18
- logger.debug "req body: #{request.body}"
19
-
20
- # https://docs.ruby-lang.org/en/master/Net/HTTPResponse.html
21
- response = http.request(request)
22
- raise HttpError, response unless response.is_a?(Net::HTTPOK)
23
-
24
- logger.debug "res body: #{response.body}"
25
- body = JSON.parse(response.body)
26
- raise JSONRpcError, body["error"] if body["error"]
27
-
28
- body["result"]
29
- end
30
- end
31
-
32
- class Eth
33
- attr_reader :url
34
-
35
- def initialize(url, logger: Logger.new($stdout, level: :info))
36
- @url = url
37
- @logger = logger
38
- end
39
-
40
- def get_block_by_bumber(block_number_or_block_tag, transaction_detail_flag = false)
41
- rpc_method = "eth_getBlockByNumber"
42
- params = [block_number_or_block_tag, transaction_detail_flag]
43
- JsonRpcClient.request(url, rpc_method, params, @logger)
44
- end
45
-
46
- # == get_block_by_bumber('latest'])['number']
47
- def block_number
48
- rpc_method = "eth_blockNumber"
49
- params = []
50
- JsonRpcClient.request(url, rpc_method, params, @logger)
51
- end
52
-
53
- #############################
54
- # use method_missing to define all the methods
55
- #############################
56
- def respond_to_missing?(*_args)
57
- true
58
- end
59
-
60
- # example:
61
- # eth.get_balance('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', 'latest')
62
- def method_missing(method, *args)
63
- words = method.to_s.split("_")
64
- rpc_method = "eth_#{words[0]}#{words[1..].collect(&:capitalize).join}"
65
- params = args
66
- JsonRpcClient.request(url, rpc_method, params, @logger)
67
- end
68
- end
69
- end
@@ -1,27 +0,0 @@
1
- require "delegate"
2
-
3
- module ThrottledJsonRpcClient
4
- class Eth < SimpleDelegator
5
- # limit: #{rate} requests / #{interval} seconds
6
- def initialize(
7
- url,
8
- rate: 5, interval: 1, redis_urls: ["redis://localhost:6379/2"],
9
- logger: Logger.new($stdout, level: :info)
10
- )
11
- @queue = DistributedRateQueue.new(
12
- redis_urls: redis_urls,
13
- key: "key:#{url}",
14
- rate: rate,
15
- interval: interval
16
- )
17
-
18
- super(JsonRpcClient::Eth.new(url, logger: logger))
19
- end
20
-
21
- def method_missing(*args, **kwargs, &block)
22
- @queue.shift do
23
- super
24
- end
25
- end
26
- end
27
- end