crea-ruby 0.0.1

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.
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