sequence-sdk 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,32 +2,43 @@ require_relative './client_module'
2
2
  require_relative './response_object'
3
3
  require_relative './query'
4
4
 
5
-
6
5
  module Sequence
7
6
  # A summation of contract amounts. Contracts are selected using a filter, and
8
7
  # their values are summed using the common values of one or more contract
9
8
  # fields.
10
9
  class Balance < ResponseObject
11
-
12
10
  # @!attribute [r] amount
13
- # Summation of contract amounts.
11
+ # Summation of contract amounts.
14
12
  # @return [Integer]
15
13
  attrib :amount
16
14
 
17
15
  # @!attribute [r] sum_by
18
- # List of parameters along which contract amounts were summed.
16
+ # List of parameters along which contract amounts were summed.
19
17
  # @return [Hash<String => String>]
20
18
  attrib :sum_by
21
19
 
22
20
  class ClientModule < Sequence::ClientModule
23
21
  # Executes a query, returning an enumerable over individual balances.
24
- # @param [Hash] opts Options hash
25
- # @option opts [String] filter A filter expression.
26
- # @option opts [Array<String|Integer>] filter_params A list of values that will be interpolated into the filter expression.
27
- # @option opts [Array<String>] sum_by A list of fields along which contract values will be summed.
28
- # @option opts [Integer] timestamp A millisecond Unix timestamp. Indicates that the query should be run over the state of the ledger at a given point in time.
22
+ # @param [Hash] opts
23
+ # Options hash
24
+ # @option opts [String] filter
25
+ # A filter expression.
26
+ # @option opts [Array<String|Integer>] filter_params
27
+ # A list of values that will be interpolated into the filter expression.
28
+ # @option opts [Array<String>] sum_by
29
+ # A list of fields along which contract values will be summed.
30
+ # @option opts [Integer>] page_size
31
+ # The number of items to return in the result set.
29
32
  # @return [Query]
30
33
  def query(opts = {})
34
+ validate_inclusion_of!(
35
+ opts,
36
+ :filter,
37
+ :filter_params,
38
+ :sum_by,
39
+ :page_size,
40
+ :after,
41
+ )
31
42
  Query.new(client, opts)
32
43
  end
33
44
  end
@@ -1,4 +1,5 @@
1
1
  require_relative './account'
2
+ require_relative './action'
2
3
  require_relative './asset'
3
4
  require_relative './balance'
4
5
  require_relative './contract'
@@ -9,26 +10,30 @@ require_relative './transaction'
9
10
 
10
11
  module Sequence
11
12
  class Client
12
-
13
- # @param [Hash] opts Options hash
14
- # @option opts [String] url The ledger API URL.
15
- # @option opts [String] access_token The ledger access token.
13
+ # @param [Hash] opts
14
+ # Options hash
15
+ # @option opts [String] ledger
16
+ # Ledger name.
17
+ # @option opts [String] credential
18
+ # API credential secret.
16
19
  # @return [Query]
17
20
  def initialize(opts = {})
18
- if !opts.key?(:ledger) || opts[:ledger].empty?
19
- raise ArgumentError.new('missing :ledger parameter')
21
+ if opts[:ledger].nil? || opts[:ledger].empty?
22
+ raise ArgumentError, ':ledger must be provided'
20
23
  end
21
- if !opts.key?(:credential) || opts[:credential].empty?
22
- raise ArgumentError.new('missing :credential parameter')
24
+ if opts[:credential].nil? || opts[:credential].empty?
25
+ raise ArgumentError, ':credential must be provided'
23
26
  end
24
27
 
25
28
  @opts = opts
26
29
  end
27
30
 
31
+ # @private
28
32
  def opts
29
33
  @opts.dup
30
34
  end
31
35
 
36
+ # @private
32
37
  # @return [Session]
33
38
  def session
34
39
  @session ||= Session.new(@opts)
@@ -44,6 +49,11 @@ module Sequence
44
49
  @assets ||= Asset::ClientModule.new(self)
45
50
  end
46
51
 
52
+ # @return [Action::ClientModule]
53
+ def actions
54
+ @actions ||= Action::ClientModule.new(self)
55
+ end
56
+
47
57
  # @return [Balance::ClientModule]
48
58
  def balances
49
59
  @balances ||= Balance::ClientModule.new(self)
@@ -64,6 +74,7 @@ module Sequence
64
74
  @contracts ||= Contract::ClientModule.new(self)
65
75
  end
66
76
 
77
+ # @private
67
78
  # @return [Stats::ClientModule]
68
79
  def stats
69
80
  @stats ||= Stats::ClientModule.new(self)
@@ -73,6 +84,5 @@ module Sequence
73
84
  def dev_utils
74
85
  @dev_utils ||= DevUtils::ClientModule.new(self)
75
86
  end
76
-
77
87
  end
78
88
  end
@@ -1,13 +1,15 @@
1
+ require_relative './validations'
2
+
1
3
  module Sequence
2
4
  # Base class for ledger client components.
3
- # @abstract
5
+ # @private
4
6
  class ClientModule
7
+ include Sequence::Validations
5
8
 
6
9
  attr_reader :client
7
10
 
8
11
  def initialize(client)
9
12
  @client = client
10
13
  end
11
-
12
14
  end
13
15
  end
@@ -6,68 +6,83 @@ module Sequence
6
6
  # An entry in the ledger that contains value that can be spent.
7
7
  class Contract < ResponseObject
8
8
  # @!attribute [r] id
9
- # A unique ID.
9
+ # A unique ID.
10
10
  # @return [String]
11
11
  attrib :id
12
12
 
13
13
  # @!attribute [r] type
14
- # The type of the contract. Currently, this is always "account".
14
+ # The type of the contract. Currently, this is always "account".
15
15
  # @return [String]
16
16
  attrib :type
17
17
 
18
18
  # @!attribute [r] transaction_id
19
- # The ID of the transaction in which the contract appears.
19
+ # The ID of the transaction in which the contract appears.
20
20
  # @return [String]
21
21
  attrib :transaction_id
22
22
 
23
23
  # @!attribute [r] asset_id
24
- # The ID of the asset held by the contract.
24
+ # The ID of the asset held by the contract.
25
25
  # @return [String]
26
26
  attrib :asset_id
27
27
 
28
28
  # @!attribute [r] asset_alias
29
- # The alias of the asset held by the contract.
29
+ # The alias of the asset held by the contract.
30
30
  # @return [String]
31
31
  attrib :asset_alias
32
32
 
33
33
  # @!attribute [r] asset_tags
34
- # The tags of the asset held by the contract.
34
+ # The tags of the asset held by the contract.
35
35
  # @return [Hash]
36
36
  attrib :asset_tags
37
37
 
38
38
  # @!attribute [r] amount
39
- # The number of units of the asset held by the contract.
39
+ # The number of units of the asset held by the contract.
40
40
  # @return [Integer]
41
41
  attrib :amount
42
42
 
43
43
  # @!attribute [r] account_id
44
- # The ID of the account controlling the contract.
44
+ # The ID of the account controlling the contract.
45
45
  # @return [String]
46
46
  attrib :account_id
47
47
 
48
48
  # @!attribute [r] account_alias
49
- # The alias of the account controlling the contract.
49
+ # The alias of the account controlling the contract.
50
50
  # @return [String]
51
51
  attrib :account_alias
52
52
 
53
53
  # @!attribute [r] account_tags
54
- # The tags of the account controlling the contract.
54
+ # The tags of the account controlling the contract.
55
55
  # @return [Hash]
56
56
  attrib :account_tags
57
57
 
58
58
  # @!attribute [r] reference_data
59
- # User-specified key-value data embedded in the contract.
59
+ # User-specified key-value data embedded in the contract.
60
60
  # @return [Hash]
61
61
  attrib :reference_data
62
62
 
63
63
  class ClientModule < Sequence::ClientModule
64
64
  # Executes a query, returning an enumerable over individual contracts.
65
- # @param [Hash] opts Options hash
66
- # @option opts [String] filter A filter expression.
67
- # @option opts [Array<String|Integer>] filter_params A list of values that will be interpolated into the filter expression.
68
- # @option opts [Integer] timestamp A millisecond Unix timestamp. Indicates that the query should be run over the state of the ledger at a given point in time.
65
+ # @param [Hash] opts
66
+ # Options hash
67
+ # @option opts [String] filter
68
+ # A filter expression.
69
+ # @option opts [Array<String|Integer>] filter_params
70
+ # A list of values that will be interpolated into the filter expression.
71
+ # @option opts [Integer] timestamp
72
+ # A millisecond Unix timestamp. Indicates that the query should be run
73
+ # over the state of the ledger at a given point in time.
74
+ # @option opts [Integer>] page_size
75
+ # The number of items to return in the result set.
69
76
  # @return [Query]
70
77
  def query(opts = {})
78
+ validate_inclusion_of!(
79
+ opts,
80
+ :filter,
81
+ :filter_params,
82
+ :timestamp,
83
+ :page_size,
84
+ :after,
85
+ )
71
86
  Query.new(client, opts)
72
87
  end
73
88
  end
@@ -8,7 +8,7 @@ module Sequence
8
8
  class ClientModule < Sequence::ClientModule
9
9
  # Deletes all data in the ledger.
10
10
  def reset
11
- client.session.request('/reset', client_token: SecureRandom.uuid)
11
+ client.session.request('/reset')
12
12
  end
13
13
  end
14
14
  end
@@ -34,23 +34,23 @@ module Sequence
34
34
  # an error code, a message, and an optional detail field that provides
35
35
  # additional context for the error.
36
36
  class APIError < BaseError
37
- RETRIABLE_STATUS_CODES = [
38
- 408, # Request Timeout
39
- 429, # Too Many Requests
40
- 500, # Internal Server Error
41
- 502, # Bad Gateway
42
- 503, # Service Unavailable
43
- 504, # Gateway Timeout
44
- 509, # Bandwidth Limit Exceeded
45
- ]
46
-
47
- attr_accessor :code, :chain_message, :detail, :data, :temporary, :request_id, :response
37
+ attr_accessor(
38
+ :chain_message,
39
+ :code,
40
+ :data,
41
+ :detail,
42
+ :request_id,
43
+ :response,
44
+ :retriable,
45
+ :temporary,
46
+ )
48
47
 
49
48
  def initialize(body, response)
50
49
  self.code = body['code']
51
50
  self.chain_message = body['message']
52
51
  self.detail = body['detail']
53
- self.temporary = body['temporary']
52
+ self.retriable = body['retriable']
53
+ self.temporary = body['retriable']
54
54
 
55
55
  self.response = response
56
56
  self.request_id = response['Chain-Request-ID'] if response
@@ -59,7 +59,7 @@ module Sequence
59
59
  end
60
60
 
61
61
  def retriable?
62
- temporary || (response && RETRIABLE_STATUS_CODES.include?(Integer(response.code)))
62
+ self.retriable
63
63
  end
64
64
 
65
65
  def self.format_error_message(code, message, detail, request_id)
@@ -2,14 +2,15 @@ require 'net/http'
2
2
  require 'net/https'
3
3
  require 'openssl'
4
4
  require 'thread'
5
+ require 'securerandom'
5
6
 
6
7
  module Sequence
8
+ # @private
7
9
  class HttpWrapper
8
-
9
10
  # Parameters to the retry exponential backoff function.
10
- MAX_RETRIES = 10
11
11
  RETRY_BASE_DELAY_MS = 40
12
- RETRY_MAX_DELAY_MS = 4000
12
+ RETRY_MAX_DELAY_MS = 20_000
13
+ RETRY_TIMEOUT_SECS = 120 # 2 minutes
13
14
 
14
15
  NETWORK_ERRORS = [
15
16
  InvalidRequestIDError,
@@ -22,46 +23,76 @@ module Sequence
22
23
  Errno::ETIMEDOUT,
23
24
  Errno::EHOSTUNREACH,
24
25
  Errno::ECONNREFUSED,
25
- ]
26
+ ].freeze
26
27
 
27
28
  attr_accessor :dis_macaroon
28
29
 
29
- def initialize(host, macaroon, opts={})
30
+ def initialize(host, macaroon, opts = {})
30
31
  @mutex = Mutex.new
31
32
  @host = URI(host)
32
33
  @macaroon = macaroon
33
34
  @dis_macaroon = nil
34
35
  @opts = opts
35
- @http = setup_http
36
+ @connection = setup_connection
36
37
  end
37
38
 
38
- def post(url, body)
39
+ def post(id, url, body)
40
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
39
41
  attempts = 0
42
+ idempotency_key = SecureRandom.uuid
40
43
  begin
41
44
  attempts += 1
42
45
  # If this is a retry and not the first attempt, sleep before making the
43
46
  # retry request.
44
47
  sleep(backoff_delay(attempts)) if attempts > 1
45
48
 
49
+ attempt_id = "#{id}/#{attempts}"
46
50
  @mutex.synchronize do
47
51
  req = Net::HTTP::Post.new(url)
48
52
  req.body = JSON.dump(body)
49
53
  req['Accept'] = 'application/json'
50
54
  req['Content-Type'] = 'application/json'
55
+ req['Id'] = attempt_id
56
+ req['Idempotency-Key'] = idempotency_key
51
57
  req['User-Agent'] = 'chain-sdk-ruby/' + Sequence::VERSION
52
- if @macaroon != nil && @dis_macaroon != nil
58
+ if !@macaroon.nil? && !@dis_macaroon.nil?
53
59
  req['Macaroon'] = @macaroon
54
60
  req['Discharge-Macaroon'] = @dis_macaroon
55
61
  end
56
- return @http.request(req)
62
+ unless @connection.started?
63
+ @connection.start
64
+ end
65
+ response = @connection.request(req)
66
+
67
+ if block_given?
68
+ yield response
69
+ end
70
+
71
+ # We must parse any APIErrors here so that
72
+ # the retry logic can handle them.
73
+ status = Integer(response.code)
74
+ parsed_body = nil
75
+ if status != 204 # No Content
76
+ begin
77
+ parsed_body = JSON.parse(response.body)
78
+ rescue JSON::JSONError
79
+ raise JSONError.new(attempt_id, response)
80
+ end
81
+ end
82
+ if status / 100 != 2
83
+ klass = status == 401 ? UnauthorizedError : APIError
84
+ raise klass.new(parsed_body, response)
85
+ end
86
+
87
+ { parsed_body: parsed_body, response: response }
57
88
  end
58
89
  rescue *NETWORK_ERRORS => e
59
- raise e if attempts > MAX_RETRIES
90
+ raise e if elapsed_secs(start_time) > RETRY_TIMEOUT_SECS
60
91
  retry
61
92
  rescue APIError => e
62
- raise e if attempts > MAX_RETRIES
63
- retry if e.retriable?
64
- raise e
93
+ raise e unless e.retriable?
94
+ raise e if elapsed_secs(start_time) > RETRY_TIMEOUT_SECS
95
+ retry
65
96
  end
66
97
  end
67
98
 
@@ -69,14 +100,18 @@ module Sequence
69
100
 
70
101
  MILLIS_TO_SEC = 0.001
71
102
 
103
+ def elapsed_secs(start_time)
104
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
105
+ end
106
+
72
107
  def backoff_delay(attempt)
73
- max = RETRY_BASE_DELAY_MS * 2**(attempt-1)
108
+ max = RETRY_BASE_DELAY_MS * 2**(attempt - 1)
74
109
  max = [max, RETRY_MAX_DELAY_MS].min
75
110
  millis = rand(max) + 1
76
111
  millis * MILLIS_TO_SEC
77
112
  end
78
113
 
79
- def setup_http
114
+ def setup_connection
80
115
  args = [@host.hostname, @host.port]
81
116
 
82
117
  # Proxy configuration
@@ -87,25 +122,24 @@ module Sequence
87
122
  end
88
123
  end
89
124
 
90
- http = Net::HTTP.new(*args)
91
- http.set_debug_output($stdout) if ENV['DEBUG']
125
+ connection = Net::HTTP.new(*args)
126
+ connection.set_debug_output($stdout) if ENV['DEBUG']
92
127
 
93
128
  # TLS configuration
94
- http.use_ssl = true
95
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
129
+ connection.use_ssl = true
130
+ connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
96
131
  [:ca_file, :cert, :key].each do |k|
97
132
  next unless @opts.key?(:ssl_params) && @opts[:ssl_params].key?(k)
98
- http.send("#{k}=", @opts[:ssl_params][k])
133
+ connection.send("#{k}=", @opts[:ssl_params][k])
99
134
  end
100
135
 
101
136
  # Timeout configuration
102
137
  [:open_timeout, :read_timeout, :ssl_timeout].each do |k|
103
138
  next unless @opts.key?(k)
104
- http.send "#{k}=", @opts[k]
139
+ connection.send "#{k}=", @opts[k]
105
140
  end
106
141
 
107
- return http
142
+ connection
108
143
  end
109
-
110
144
  end
111
145
  end