throttled_json_rpc_client 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3d75ceb289344a401badd6873381a592e0fa445a4b65baf0ca369d53bf3fd6ed
4
- data.tar.gz: 66eafc5b5be6cb4a95080f6050cc7cc9baa21e48cc3db15d4fae8d3fc942ed59
3
+ metadata.gz: 59ebf0f85dc101410fcd0a3a665777ed1ffbae6e662e895acf7e338756cb8155
4
+ data.tar.gz: 58cee4e703c32b3a771b90bb5adc801f96d22b4359ff730ecac9820e8afaab91
5
5
  SHA512:
6
- metadata.gz: 2c72d92f7ffca6c3f627e356d6a52a7a857e5a063a84182ce222bc25ad2c11d31ba3e138041648ebd30a2dc6074f12d49869f3da103915b040c3825b5b58fcf1
7
- data.tar.gz: cfed9cb9aa7d1a41f36e373c4cd7744a3fb2e7d0f88c830bf64050ad8e40f4540602c30efe05fdd9e30dc64a49a89154fcfc815371c7a8cf127b47beb2c78433
6
+ metadata.gz: f6491a2d5685dae9e50c264271b6c07cb8701cb87c5b65709dfbcc4f594e3be7059deab89cc450a61402a32f7bd1a488dbd6f777bb189066ddc55d2600144a98
7
+ data.tar.gz: 237c09309c9f13de9b72660af01b8266ebc1f78deb9f31de11c71a7fba263e57611a503c19910091c5333e90bf11ee76f149f92020d4ed0f5509506dda88b2fb
data/README.md CHANGED
@@ -14,33 +14,23 @@ 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)
43
32
  ```
33
+ see [example](./example.rb)
44
34
 
45
35
  ## Development
46
36
 
data/example.rb CHANGED
@@ -1,14 +1,17 @@
1
- rpc_url = "https://1rpc.io/eth"
1
+ rpc_url = "https://eth.llamarpc.com"
2
2
 
3
- eth = ThrottledJsonRpcClient::Eth.new(
3
+ # limit: 1 request / 5 seconds
4
+ client = ThrottledJsonRpcClient::Client.new(
4
5
  rpc_url,
5
- redis_urls: ["redis://redis:6379/2"]
6
+ rate: 1,
7
+ interval: 5,
8
+ redis_urls: ["redis://localhost:6379/2"]
6
9
  )
7
10
 
8
11
  threads = []
9
12
  10.times do
10
13
  threads << Thread.new do
11
- p eth.block_number
14
+ p client.call("eth_blockNumber", [])
12
15
  end
13
16
  end
14
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.0"
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.0
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-18 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,61 +0,0 @@
1
- module JsonRpcClient
2
- class HttpError < StandardError; end
3
- class JSONRpcError < StandardError; end
4
-
5
- class << self
6
- def request(url, method, params)
7
- uri = URI.parse(url)
8
- http = Net::HTTP.new(uri.host, uri.port)
9
- http.use_ssl = uri.scheme == "https"
10
-
11
- request = Net::HTTP::Post.new(uri, "Content-Type" => "application/json")
12
- request.body = { jsonrpc: "2.0", method: method, params: params, id: 1 }.to_json
13
-
14
- # https://docs.ruby-lang.org/en/master/Net/HTTPResponse.html
15
- response = http.request(request)
16
- raise HttpError, response unless response.is_a?(Net::HTTPOK)
17
-
18
- body = JSON.parse(response.body)
19
- raise JSONRpcError, body["error"] if body["error"]
20
-
21
- body["result"]
22
- end
23
- end
24
-
25
- class Eth
26
- attr_reader :url
27
-
28
- def initialize(url)
29
- @url = url
30
- end
31
-
32
- def get_block_by_bumber(block_number_or_block_tag, transaction_detail_flag = false)
33
- rpc_method = "eth_getBlockByNumber"
34
- params = [block_number_or_block_tag, transaction_detail_flag]
35
- JsonRpcClient.request(url, rpc_method, params)
36
- end
37
-
38
- # == get_block_by_bumber('latest'])['number']
39
- def block_number
40
- rpc_method = "eth_blockNumber"
41
- params = []
42
- JsonRpcClient.request(url, rpc_method, params)
43
- end
44
-
45
- #############################
46
- # use method_missing to define all the methods
47
- #############################
48
- def respond_to_missing?(*_args)
49
- true
50
- end
51
-
52
- # example:
53
- # eth.get_balance('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', 'latest')
54
- def method_missing(method, *args)
55
- words = method.to_s.split("_")
56
- rpc_method = "eth_#{words[0]}#{words[1..].collect(&:capitalize).join}"
57
- params = args
58
- JsonRpcClient.request(url, rpc_method, params)
59
- end
60
- end
61
- end
@@ -1,23 +0,0 @@
1
- require "delegate"
2
-
3
- module ThrottledJsonRpcClient
4
- class Eth < SimpleDelegator
5
- # limit: #{rate} requests / #{interval} seconds
6
- def initialize(url, rate: 5, interval: 1, redis_urls: ["redis://localhost:6379/2"])
7
- @queue = DistributedRateQueue.new(
8
- redis_urls: redis_urls,
9
- key: "key:#{url}",
10
- rate: rate,
11
- interval: interval
12
- )
13
-
14
- super(JsonRpcClient::Eth.new(url))
15
- end
16
-
17
- def method_missing(*args, **kwargs, &block)
18
- @queue.shift do
19
- super
20
- end
21
- end
22
- end
23
- end