crea-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +55 -0
  3. data/CONTRIBUTING.md +79 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE +22 -0
  6. data/README.md +234 -0
  7. data/Rakefile +332 -0
  8. data/crea-ruby.gemspec +39 -0
  9. data/gource.sh +6 -0
  10. data/lib/crea.rb +85 -0
  11. data/lib/crea/api.rb +208 -0
  12. data/lib/crea/base_error.rb +218 -0
  13. data/lib/crea/block_api.rb +78 -0
  14. data/lib/crea/broadcast.rb +1334 -0
  15. data/lib/crea/chain_config.rb +36 -0
  16. data/lib/crea/formatter.rb +14 -0
  17. data/lib/crea/jsonrpc.rb +108 -0
  18. data/lib/crea/marshal.rb +231 -0
  19. data/lib/crea/mixins/jsonable.rb +37 -0
  20. data/lib/crea/mixins/retriable.rb +58 -0
  21. data/lib/crea/mixins/serializable.rb +45 -0
  22. data/lib/crea/operation.rb +141 -0
  23. data/lib/crea/operation/account_create.rb +10 -0
  24. data/lib/crea/operation/account_create_with_delegation.rb +12 -0
  25. data/lib/crea/operation/account_update.rb +8 -0
  26. data/lib/crea/operation/account_witness_proxy.rb +4 -0
  27. data/lib/crea/operation/account_witness_vote.rb +5 -0
  28. data/lib/crea/operation/cancel_transfer_from_savings.rb +4 -0
  29. data/lib/crea/operation/challenge_authority.rb +5 -0
  30. data/lib/crea/operation/change_recovery_account.rb +5 -0
  31. data/lib/crea/operation/claim_account.rb +5 -0
  32. data/lib/crea/operation/claim_reward_balance.rb +6 -0
  33. data/lib/crea/operation/comment.rb +9 -0
  34. data/lib/crea/operation/comment_options.rb +10 -0
  35. data/lib/crea/operation/convert.rb +5 -0
  36. data/lib/crea/operation/create_claimed_account.rb +10 -0
  37. data/lib/crea/operation/custom.rb +5 -0
  38. data/lib/crea/operation/custom_binary.rb +8 -0
  39. data/lib/crea/operation/custom_json.rb +6 -0
  40. data/lib/crea/operation/decline_voting_rights.rb +4 -0
  41. data/lib/crea/operation/delegate_vesting_shares.rb +5 -0
  42. data/lib/crea/operation/delete_comment.rb +4 -0
  43. data/lib/crea/operation/escrow_approve.rb +8 -0
  44. data/lib/crea/operation/escrow_dispute.rb +7 -0
  45. data/lib/crea/operation/escrow_release.rb +10 -0
  46. data/lib/crea/operation/escrow_transfer.rb +12 -0
  47. data/lib/crea/operation/feed_publish.rb +4 -0
  48. data/lib/crea/operation/limit_order_cancel.rb +4 -0
  49. data/lib/crea/operation/limit_order_create.rb +8 -0
  50. data/lib/crea/operation/limit_order_create2.rb +8 -0
  51. data/lib/crea/operation/prove_authority.rb +4 -0
  52. data/lib/crea/operation/recover_account.rb +6 -0
  53. data/lib/crea/operation/report_over_production.rb +5 -0
  54. data/lib/crea/operation/request_account_recovery.rb +6 -0
  55. data/lib/crea/operation/reset_account.rb +5 -0
  56. data/lib/crea/operation/set_reset_account.rb +5 -0
  57. data/lib/crea/operation/set_withdraw_vesting_route.rb +6 -0
  58. data/lib/crea/operation/transfer.rb +6 -0
  59. data/lib/crea/operation/transfer_from_savings.rb +7 -0
  60. data/lib/crea/operation/transfer_to_savings.rb +6 -0
  61. data/lib/crea/operation/transfer_to_vesting.rb +5 -0
  62. data/lib/crea/operation/vote.rb +6 -0
  63. data/lib/crea/operation/withdraw_vesting.rb +4 -0
  64. data/lib/crea/operation/witness_set_properties.rb +5 -0
  65. data/lib/crea/operation/witness_update.rb +7 -0
  66. data/lib/crea/rpc/base_client.rb +179 -0
  67. data/lib/crea/rpc/http_client.rb +143 -0
  68. data/lib/crea/rpc/thread_safe_http_client.rb +35 -0
  69. data/lib/crea/stream.rb +385 -0
  70. data/lib/crea/transaction.rb +96 -0
  71. data/lib/crea/transaction_builder.rb +393 -0
  72. data/lib/crea/type/amount.rb +107 -0
  73. data/lib/crea/type/base_type.rb +10 -0
  74. data/lib/crea/utils.rb +17 -0
  75. data/lib/crea/version.rb +4 -0
  76. metadata +478 -0
@@ -0,0 +1,6 @@
1
+ class Crea::Operation::RecoverAccount < Crea::Operation
2
+ def_attr account_to_recover: :string
3
+ def_attr new_owner_authority: :authority
4
+ def_attr recent_owner_authority: :authority
5
+ def_attr extensions: :empty_array
6
+ end
@@ -0,0 +1,5 @@
1
+ class Crea::Operation::ReportOverProduction < Crea::Operation
2
+ def_attr reporter: :string
3
+ def_attr first_block: :string # FIXME signed_block_header
4
+ def_attr second_block: :string # FIXME signed_block_header
5
+ end
@@ -0,0 +1,6 @@
1
+ class Crea::Operation::RequestAccountRecovery < Crea::Operation
2
+ def_attr recovery_account: :string
3
+ def_attr account_to_recover: :string
4
+ def_attr new_owner_authority: :authority
5
+ def_attr extensions: :empty_array
6
+ end
@@ -0,0 +1,5 @@
1
+ class Crea::Operation::ResetAccount < Crea::Operation
2
+ def_attr reset_account: :string
3
+ def_attr account_to_reset: :string
4
+ def_attr new_owner_authority: :authority
5
+ end
@@ -0,0 +1,5 @@
1
+ class Crea::Operation::SetResetAccount < Crea::Operation
2
+ def_attr account: :string
3
+ def_attr current_reset_account: :string
4
+ def_attr reset_account: :string
5
+ end
@@ -0,0 +1,6 @@
1
+ class Crea::Operation::SetWithdrawVestingRoute < Crea::Operation
2
+ def_attr from: :string
3
+ def_attr to: :string
4
+ def_attr percent: :uint16
5
+ def_attr auto_vest: :boolean
6
+ end
@@ -0,0 +1,6 @@
1
+ class Crea::Operation::Transfer < Crea::Operation
2
+ def_attr from: :string
3
+ def_attr to: :string
4
+ def_attr amount: :amount
5
+ def_attr memo: :string
6
+ end
@@ -0,0 +1,7 @@
1
+ class Crea::Operation::TransferFromSavings < Crea::Operation
2
+ def_attr from: :string
3
+ def_attr request_id: :uint32
4
+ def_attr to: :string
5
+ def_attr amount: :amount
6
+ def_attr memo: :string
7
+ end
@@ -0,0 +1,6 @@
1
+ class Crea::Operation::TransferToSavings < Crea::Operation
2
+ def_attr from: :string
3
+ def_attr to: :string
4
+ def_attr amount: :amount
5
+ def_attr memo: :string
6
+ end
@@ -0,0 +1,5 @@
1
+ class Crea::Operation::TransferToVesting < Crea::Operation
2
+ def_attr from: :string
3
+ def_attr to: :string
4
+ def_attr amount: :amount
5
+ end
@@ -0,0 +1,6 @@
1
+ class Crea::Operation::Vote < Crea::Operation
2
+ def_attr voter: :string
3
+ def_attr author: :string
4
+ def_attr permlink: :string
5
+ def_attr weight: :int16
6
+ end
@@ -0,0 +1,4 @@
1
+ class Crea::Operation::WithdrawVesting < Crea::Operation
2
+ def_attr account: :string
3
+ def_attr vesting_shares: :amount
4
+ end
@@ -0,0 +1,5 @@
1
+ class Crea::Operation::WitnessSetProperties < Crea::Operation
2
+ def_attr owner: :string
3
+ def_attr props: :witness_properties
4
+ def_attr extensions: :empty_array
5
+ end
@@ -0,0 +1,7 @@
1
+ class Crea::Operation::WitnessUpdate < Crea::Operation
2
+ def_attr owner: :string
3
+ def_attr url: :string
4
+ def_attr block_signing_key: :public_key
5
+ def_attr props: :chain_properties
6
+ def_attr fee: :amount
7
+ end
@@ -0,0 +1,179 @@
1
+ module Crea
2
+ module RPC
3
+ class BaseClient
4
+ include ChainConfig
5
+
6
+ attr_accessor :url, :chain, :error_pipe
7
+
8
+ # @private
9
+ MAX_TIMEOUT_RETRY_COUNT = 100
10
+
11
+ # @private
12
+ MAX_TIMEOUT_BACKOFF = 30
13
+
14
+ # @private
15
+ TIMEOUT_ERRORS = [Net::ReadTimeout, Errno::EBADF, IOError]
16
+
17
+ def initialize(options = {})
18
+ @chain = options[:chain] || :crea
19
+ @error_pipe = options[:error_pipe] || STDERR
20
+ @api_name = options[:api_name]
21
+ @url = case @chain
22
+ when :crea then options[:url] || NETWORKS_CREA_DEFAULT_NODE
23
+ when :test then options[:url] || NETWORKS_TEST_DEFAULT_NODE
24
+ else; raise UnsupportedChainError, "Unsupported chain: #{@chain}"
25
+ end
26
+ end
27
+
28
+ def uri
29
+ @uri ||= URI.parse(url)
30
+ end
31
+
32
+ # Adds a request object to the stack. Usually, this method is called
33
+ # internally by {BaseClient#rpc_execute}. If you want to create a batched
34
+ # request, use this method to add to the batch then execute {BaseClient#rpc_batch_execute}.
35
+ def put(api_name = @api_name, api_method = nil, options = {})
36
+ current_rpc_id = rpc_id
37
+ rpc_method_name = "#{api_name}.#{api_method}"
38
+ options ||= {}
39
+ request_object = defined?(options.delete) ? options.delete(:request_object) : []
40
+ request_object ||= []
41
+
42
+ request_object << {
43
+ jsonrpc: '2.0',
44
+ id: current_rpc_id,
45
+ method: rpc_method_name,
46
+ params: options
47
+ }
48
+
49
+ request_object
50
+ end
51
+
52
+ # @abstract Subclass is expected to implement #rpc_execute.
53
+ # @!method rpc_execute
54
+
55
+ # @abstract Subclass is expected to implement #rpc_batch_execute.
56
+ # @!method rpc_batch_execute
57
+
58
+ # To be called by {BaseClient#rpc_execute} and {BaseClient#rpc_batch_execute}
59
+ # when a response has been consructed.
60
+ def yield_response(response, &block)
61
+ if !!block
62
+ case response
63
+ when Hashie::Mash then yield response.result, response.error, response.id
64
+ when Hashie::Array
65
+ response.each do |r|
66
+ r = Hashie::Mash.new(r)
67
+ block.call r.result, r.error, r.id
68
+ end
69
+ else; block.call response
70
+ end
71
+ end
72
+
73
+ response
74
+ end
75
+
76
+ # Checks json-rpc request/response for corrilated id. If they do not
77
+ # match, {IncorrectResponseIdError} is thrown. This is usually caused by
78
+ # the client, involving thread safety. It can also be caused by the node
79
+ # responding without an id.
80
+ #
81
+ # To avoid {IncorrectResponseIdError}, make sure you implement your client
82
+ # correctly.
83
+ #
84
+ # Setting DEBUG=true in the envrionment will cause this method to output
85
+ # both the request and response json.
86
+ #
87
+ # @param options [Hash] options
88
+ # @option options [Boolean] :debug Enable or disable debug output.
89
+ # @option options [Hash] :request to compare id
90
+ # @option options [Hash] :response to compare id
91
+ # @option options [String] :api_method
92
+ # @see {ThreadSafeHttpClient}
93
+ def evaluate_id(options = {})
94
+ debug = options[:debug] || ENV['DEBUG'] == 'true'
95
+ request = options[:request]
96
+ response = options[:response]
97
+ api_method = options[:api_method]
98
+ req_id = request[:id].to_i
99
+ res_id = !!response['id'] ? response['id'].to_i : nil
100
+ method = [@api_name, api_method].join('.')
101
+
102
+ if debug
103
+ req = JSON.pretty_generate(request)
104
+ res = JSON.parse(response) rescue response
105
+ res = JSON.pretty_generate(response) rescue response
106
+
107
+ error_pipe.puts '=' * 80
108
+ error_pipe.puts "Request:"
109
+ error_pipe.puts req
110
+ error_pipe.puts '=' * 80
111
+ error_pipe.puts "Response:"
112
+ error_pipe.puts res
113
+ error_pipe.puts '=' * 80
114
+ error_pipe.puts Thread.current.backtrace.join("\n")
115
+ end
116
+
117
+ error = response['error'].to_json if !!response['error']
118
+
119
+ if req_id != res_id
120
+ raise IncorrectResponseIdError, "#{method}: The json-rpc id did not match. Request was: #{req_id}, got: #{res_id.inspect}", BaseError.send(:build_backtrace, error)
121
+ end
122
+ end
123
+
124
+ # Current json-rpc id used for a request. This version auto-increments
125
+ # for each call. Subclasses can use their own strategy.
126
+ def rpc_id
127
+ @rpc_id ||= 0
128
+ @rpc_id += 1
129
+ end
130
+ private
131
+ # @private
132
+ def reset_timeout
133
+ @timeout_retry_count = 0
134
+ @back_off = 0.1
135
+ end
136
+
137
+ # @private
138
+ def retry_timeout(context, cause = nil)
139
+ @timeout_retry_count += 1
140
+
141
+ if @timeout_retry_count > MAX_TIMEOUT_RETRY_COUNT
142
+ raise TooManyTimeoutsError.new("Too many timeouts for: #{context}", cause)
143
+ elsif @timeout_retry_count % 10 == 0
144
+ msg = "#{@timeout_retry_count} retry attempts for: #{context}"
145
+ msg += "; cause: #{cause}" if !!cause
146
+ error_pipe.puts msg
147
+ end
148
+
149
+ backoff_timeout
150
+
151
+ context
152
+ end
153
+
154
+ # Expontential backoff.
155
+ #
156
+ # @private
157
+ def backoff_timeout
158
+ @backoff ||= 0.1
159
+ @backoff *= 2
160
+ @backoff = 0.1 if @backoff > MAX_TIMEOUT_BACKOFF
161
+
162
+ sleep @backoff
163
+ end
164
+
165
+ # @private
166
+ def raise_error_response(rpc_method_name, rpc_args, response)
167
+ raise UnknownError, "#{rpc_method_name}: #{response}" if response.error.nil?
168
+
169
+ error = response.error
170
+
171
+ if error.message == 'Invalid Request'
172
+ raise Crea::ArgumentError, "Unexpected arguments: #{rpc_args.inspect}. Expected: #{rpc_method_name} (#{args_keys_to_s(rpc_method_name)})"
173
+ end
174
+
175
+ BaseError.build_error(error, rpc_method_name)
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,143 @@
1
+ module Crea
2
+ module RPC
3
+ # {HttpClient} is intended for single-threaded applications. For
4
+ # multi-threaded apps, use {ThreadSafeHttpClient}.
5
+ class HttpClient < BaseClient
6
+ # Timeouts are lower level errors, related in that retrying them is
7
+ # trivial, unlike, for example TransactionExpiredError, that *requires*
8
+ # the client to do something before retrying.
9
+ #
10
+ # These situations are hopefully momentary interruptions or rate limiting
11
+ # but they might indicate a bigger problem with the node, so they are not
12
+ # retried forever, only up to MAX_TIMEOUT_RETRY_COUNT and then we give up.
13
+ #
14
+ # *Note:* {JSON::ParserError} is included in this list because under
15
+ # certain timeout conditions, a web server may respond with a generic
16
+ # http status code of 200 and HTML page.
17
+ #
18
+ # @private
19
+ TIMEOUT_ERRORS = [Net::OpenTimeout, JSON::ParserError, Net::ReadTimeout,
20
+ Errno::EBADF, IOError, Errno::ENETDOWN, Crea::RemoteDatabaseLockError]
21
+
22
+ # @private
23
+ POST_HEADERS = {
24
+ 'Content-Type' => 'application/json; charset=utf-8',
25
+ 'User-Agent' => Crea::AGENT_ID
26
+ }
27
+
28
+ JSON_RPC_BATCH_SIZE_MAXIMUM = 50
29
+
30
+ def http
31
+ @http ||= Net::HTTP.new(uri.host, uri.port).tap do |http|
32
+ http.use_ssl = true if uri.to_s =~ /^https/i
33
+ http.keep_alive_timeout = 150 # seconds
34
+
35
+ # WARNING This method opens a serious security hole. Never use this
36
+ # method in production code.
37
+ # http.set_debug_output(STDOUT) if !!ENV['DEBUG']
38
+ end
39
+ end
40
+
41
+ def http_post
42
+ @http_post ||= Net::HTTP::Post.new(uri.request_uri, POST_HEADERS)
43
+ end
44
+
45
+ def http_request(request)
46
+ http.request(request)
47
+ end
48
+
49
+ # This is the main method used by API instances to actually fetch data
50
+ # from the remote node. It abstracts the api namespace, method name, and
51
+ # parameters so that the API instance can be decoupled from the protocol.
52
+ #
53
+ # @param api_name [String] API namespace of the method being called.
54
+ # @param api_method [String] API method name being called.
55
+ # @param options [Hash] options
56
+ # @option options [Object] :request_object Hash or Array to become json in request body.
57
+ def rpc_execute(api_name = @api_name, api_method = nil, options = {}, &block)
58
+ reset_timeout
59
+
60
+ catch :tota_cera_pila do; begin
61
+ request = http_post
62
+
63
+ request_object = if !!api_name && !!api_method
64
+ put(api_name, api_method, options)
65
+ elsif !!options && defined?(options.delete)
66
+ options.delete(:request_object)
67
+ end
68
+
69
+ if request_object.size > JSON_RPC_BATCH_SIZE_MAXIMUM
70
+ raise JsonRpcBatchMaximumSizeExceededError, "Maximum json-rpc-batch is #{JSON_RPC_BATCH_SIZE_MAXIMUM} elements."
71
+ end
72
+
73
+ request.body = if request_object.class == Hash
74
+ request_object
75
+ elsif request_object.size == 1
76
+ request_object.first
77
+ else
78
+ request_object
79
+ end.to_json
80
+
81
+ response = catch :http_request do; begin; http_request(request)
82
+ rescue *TIMEOUT_ERRORS => e
83
+ throw retry_timeout(:http_request, e)
84
+ end; end
85
+
86
+ if response.nil?
87
+ throw retry_timeout(:tota_cera_pila, 'response was nil')
88
+ end
89
+
90
+ case response.code
91
+ when '200'
92
+ response = catch :parse_json do; begin; JSON[response.body]
93
+ rescue *TIMEOUT_ERRORS => e
94
+ throw retry_timeout(:parse_json, e)
95
+ end; end
96
+
97
+ response = case response
98
+ when Hash
99
+ Hashie::Mash.new(response).tap do |r|
100
+ evaluate_id(request: request_object.first, response: r, api_method: api_method)
101
+ end
102
+ when Array
103
+ Hashie::Array.new(response).tap do |r|
104
+ request_object.each_with_index do |req, index|
105
+ evaluate_id(request: req, response: r[index], api_method: api_method)
106
+ end
107
+ end
108
+ else; response
109
+ end
110
+
111
+ [response].flatten.each_with_index do |r, i|
112
+ if defined?(r.error) && !!r.error
113
+ if !!r.error.message
114
+ begin
115
+ rpc_method_name = "#{api_name}.#{api_method}"
116
+ rpc_args = [request_object].flatten[i]
117
+ raise_error_response rpc_method_name, rpc_args, r
118
+ rescue *TIMEOUT_ERRORS => e
119
+ throw retry_timeout(:tota_cera_pila, e)
120
+ end
121
+ else
122
+ raise Crea::ArgumentError, r.error.inspect
123
+ end
124
+ end
125
+ end
126
+
127
+ yield_response response, &block
128
+ when '504' # Gateway Timeout
129
+ throw retry_timeout(:tota_cera_pila, response.body)
130
+ when '502' # Bad Gateway
131
+ throw retry_timeout(:tota_cera_pila, response.body)
132
+ else
133
+ raise UnknownError, "#{api_name}.#{api_method}: #{response.body}"
134
+ end
135
+ end; end
136
+ end
137
+
138
+ def rpc_batch_execute(options = {}, &block)
139
+ yield_response rpc_execute(nil, nil, options), &block
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,35 @@
1
+ module Crea
2
+ module RPC
3
+ # {ThreadSafeHttpClient} is the default RPC Client used by `crea-ruby.`
4
+ # It's perfect for simple requests. But for higher performance, it's better
5
+ # to override {HttpClient} and implement something other than {Net::HTTP}.
6
+ #
7
+ # It performs http requests in a {Mutex} critical section because {Net::HTTP}
8
+ # is not thread safe. This is the very minimum level thread safety
9
+ # available.
10
+ class ThreadSafeHttpClient < HttpClient
11
+ SEMAPHORE = Mutex.new.freeze
12
+
13
+ # Same as #{HttpClient#http_post}, but scoped to each thread so it is
14
+ # thread safe.
15
+ def http_post
16
+ thread = Thread.current
17
+ http_post = thread.thread_variable_get(:http_post)
18
+ http_post ||= Net::HTTP::Post.new(uri.request_uri, POST_HEADERS)
19
+ thread.thread_variable_set(:http_post, http_post)
20
+ end
21
+
22
+ def http_request(request); SEMAPHORE.synchronize{super}; end
23
+
24
+ # Same as #{BaseClient#rpc_id}, auto-increment, but scoped to each thread
25
+ # so it is thread safe.
26
+ def rpc_id
27
+ thread = Thread.current
28
+ rpc_id = thread.thread_variable_get(:rpc_id)
29
+ rpc_id ||= 0
30
+ rpc_id += 1
31
+ thread.thread_variable_set(:rpc_id, rpc_id)
32
+ end
33
+ end
34
+ end
35
+ end