throttled_json_rpc_client 0.1.0 → 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: 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