hive-ruby 1.0.0.pre.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.
- checksums.yaml +7 -0
- data/.gitignore +54 -0
- data/CONTRIBUTING.md +79 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +91 -0
- data/LICENSE +21 -0
- data/README.md +248 -0
- data/Rakefile +358 -0
- data/gource.sh +6 -0
- data/hive-ruby.gemspec +40 -0
- data/images/Anthony Martin.png +0 -0
- data/lib/hive.rb +89 -0
- data/lib/hive/api.rb +223 -0
- data/lib/hive/base_error.rb +218 -0
- data/lib/hive/block_api.rb +78 -0
- data/lib/hive/bridge.rb +12 -0
- data/lib/hive/broadcast.rb +1334 -0
- data/lib/hive/chain_config.rb +34 -0
- data/lib/hive/fallback.rb +287 -0
- data/lib/hive/formatter.rb +14 -0
- data/lib/hive/jsonrpc.rb +112 -0
- data/lib/hive/marshal.rb +231 -0
- data/lib/hive/mixins/jsonable.rb +37 -0
- data/lib/hive/mixins/retriable.rb +58 -0
- data/lib/hive/mixins/serializable.rb +45 -0
- data/lib/hive/operation.rb +141 -0
- data/lib/hive/operation/account_create.rb +10 -0
- data/lib/hive/operation/account_create_with_delegation.rb +12 -0
- data/lib/hive/operation/account_update.rb +8 -0
- data/lib/hive/operation/account_witness_proxy.rb +4 -0
- data/lib/hive/operation/account_witness_vote.rb +5 -0
- data/lib/hive/operation/cancel_transfer_from_savings.rb +4 -0
- data/lib/hive/operation/challenge_authority.rb +5 -0
- data/lib/hive/operation/change_recovery_account.rb +5 -0
- data/lib/hive/operation/claim_account.rb +5 -0
- data/lib/hive/operation/claim_reward_balance.rb +6 -0
- data/lib/hive/operation/comment.rb +9 -0
- data/lib/hive/operation/comment_options.rb +10 -0
- data/lib/hive/operation/convert.rb +5 -0
- data/lib/hive/operation/create_claimed_account.rb +10 -0
- data/lib/hive/operation/custom.rb +5 -0
- data/lib/hive/operation/custom_binary.rb +8 -0
- data/lib/hive/operation/custom_json.rb +6 -0
- data/lib/hive/operation/decline_voting_rights.rb +4 -0
- data/lib/hive/operation/delegate_vesting_shares.rb +5 -0
- data/lib/hive/operation/delete_comment.rb +4 -0
- data/lib/hive/operation/escrow_approve.rb +8 -0
- data/lib/hive/operation/escrow_dispute.rb +7 -0
- data/lib/hive/operation/escrow_release.rb +10 -0
- data/lib/hive/operation/escrow_transfer.rb +12 -0
- data/lib/hive/operation/feed_publish.rb +4 -0
- data/lib/hive/operation/limit_order_cancel.rb +4 -0
- data/lib/hive/operation/limit_order_create.rb +8 -0
- data/lib/hive/operation/limit_order_create2.rb +8 -0
- data/lib/hive/operation/prove_authority.rb +4 -0
- data/lib/hive/operation/recover_account.rb +6 -0
- data/lib/hive/operation/report_over_production.rb +5 -0
- data/lib/hive/operation/request_account_recovery.rb +6 -0
- data/lib/hive/operation/reset_account.rb +5 -0
- data/lib/hive/operation/set_reset_account.rb +5 -0
- data/lib/hive/operation/set_withdraw_vesting_route.rb +6 -0
- data/lib/hive/operation/transfer.rb +6 -0
- data/lib/hive/operation/transfer_from_savings.rb +7 -0
- data/lib/hive/operation/transfer_to_savings.rb +6 -0
- data/lib/hive/operation/transfer_to_vesting.rb +5 -0
- data/lib/hive/operation/vote.rb +6 -0
- data/lib/hive/operation/withdraw_vesting.rb +4 -0
- data/lib/hive/operation/witness_set_properties.rb +5 -0
- data/lib/hive/operation/witness_update.rb +7 -0
- data/lib/hive/rpc/base_client.rb +179 -0
- data/lib/hive/rpc/http_client.rb +143 -0
- data/lib/hive/rpc/thread_safe_http_client.rb +35 -0
- data/lib/hive/stream.rb +385 -0
- data/lib/hive/transaction.rb +115 -0
- data/lib/hive/transaction_builder.rb +406 -0
- data/lib/hive/type/amount.rb +126 -0
- data/lib/hive/type/base_type.rb +10 -0
- data/lib/hive/utils.rb +17 -0
- data/lib/hive/version.rb +4 -0
- metadata +502 -0
@@ -0,0 +1,143 @@
|
|
1
|
+
module Hive
|
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, Hive::RemoteDatabaseLockError]
|
21
|
+
|
22
|
+
# @private
|
23
|
+
POST_HEADERS = {
|
24
|
+
'Content-Type' => 'application/json; charset=utf-8',
|
25
|
+
'User-Agent' => Hive::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 Hive::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 Hive
|
2
|
+
module RPC
|
3
|
+
# {ThreadSafeHttpClient} is the default RPC Client used by `hive-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
|
data/lib/hive/stream.rb
ADDED
@@ -0,0 +1,385 @@
|
|
1
|
+
module Hive
|
2
|
+
# Hive::Stream allows a live view of the HIVE blockchain.
|
3
|
+
#
|
4
|
+
# Example streaming blocks:
|
5
|
+
#
|
6
|
+
# stream = Hive::Stream.new
|
7
|
+
#
|
8
|
+
# stream.blocks do |block, block_num|
|
9
|
+
# puts "#{block_num} :: #{block.witness}"
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# Example streaming transactions:
|
13
|
+
#
|
14
|
+
# stream = Hive::Stream.new
|
15
|
+
#
|
16
|
+
# stream.transactions do |trx, trx_id, block_num|
|
17
|
+
# puts "#{block_num} :: #{trx_id} :: operations: #{trx.operations.size}"
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# Example streaming operations:
|
21
|
+
#
|
22
|
+
# stream = Hive::Stream.new
|
23
|
+
#
|
24
|
+
# stream.operations do |op, trx_id, block_num|
|
25
|
+
# puts "#{block_num} :: #{trx_id} :: #{op.type}: #{op.value.to_json}"
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# Allows streaming of block headers, full blocks, transactions, operations and
|
29
|
+
# virtual operations.
|
30
|
+
class Stream
|
31
|
+
attr_reader :database_api, :block_api, :account_history_api, :mode
|
32
|
+
|
33
|
+
BLOCK_INTERVAL = 3
|
34
|
+
MAX_BACKOFF_BLOCK_INTERVAL = 30
|
35
|
+
MAX_RETRY_COUNT = 10
|
36
|
+
|
37
|
+
VOP_TRX_ID = ('0' * 40).freeze
|
38
|
+
|
39
|
+
# @param options [Hash] additional options
|
40
|
+
# @option options [Hive::DatabaseApi] :database_api
|
41
|
+
# @option options [Hive::BlockApi] :block_api
|
42
|
+
# @option options [Hive::AccountHistoryApi || Hive::CondenserApi] :account_history_api
|
43
|
+
# @option options [Symbol] :mode we have the choice between
|
44
|
+
# * :head the last block
|
45
|
+
# * :irreversible the block that is confirmed by 2/3 of all block producers and is thus irreversible!
|
46
|
+
# @option options [Boolean] :no_warn do not generate warnings
|
47
|
+
def initialize(options = {mode: :irreversible})
|
48
|
+
@instance_options = options
|
49
|
+
@database_api = options[:database_api] || Hive::DatabaseApi.new(options)
|
50
|
+
@block_api = options[:block_api] || Hive::BlockApi.new(options)
|
51
|
+
@account_history_api = options[:account_history_api]
|
52
|
+
@mode = options[:mode] || :irreversible
|
53
|
+
@no_warn = !!options[:no_warn]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Use this method to stream block numbers. This is significantly faster
|
57
|
+
# than requesting full blocks and even block headers. Basically, the only
|
58
|
+
# thing this method does is call {Hive::Database#get_dynamic_global_properties} at 3 second
|
59
|
+
# intervals.
|
60
|
+
#
|
61
|
+
# @param options [Hash] additional options
|
62
|
+
# @option options [Integer] :at_block_num Starts the stream at the given block number. Default: nil.
|
63
|
+
# @option options [Integer] :until_block_num Ends the stream at the given block number. Default: nil.
|
64
|
+
def block_numbers(options = {}, &block)
|
65
|
+
block_objects(options.merge(object: :block_numbers), block)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Use this method to stream block headers. This is quite a bit faster than
|
69
|
+
# requesting full blocks.
|
70
|
+
#
|
71
|
+
# @param options [Hash] additional options
|
72
|
+
# @option options [Integer] :at_block_num Starts the stream at the given block number. Default: nil.
|
73
|
+
# @option options [Integer] :until_block_num Ends the stream at the given block number. Default: nil.
|
74
|
+
def block_headers(options = {}, &block)
|
75
|
+
block_objects(options.merge(object: :block_headers), block)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Use this method to stream full blocks.
|
79
|
+
#
|
80
|
+
# @param options [Hash] additional options
|
81
|
+
# @option options [Integer] :at_block_num Starts the stream at the given block number. Default: nil.
|
82
|
+
# @option options [Integer] :until_block_num Ends the stream at the given block number. Default: nil.
|
83
|
+
def blocks(options = {}, &block)
|
84
|
+
block_objects(options.merge(object: :blocks), block)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Use this method to stream each transaction.
|
88
|
+
#
|
89
|
+
# @param options [Hash] additional options
|
90
|
+
# @option options [Integer] :at_block_num Starts the stream at the given block number. Default: nil.
|
91
|
+
# @option options [Integer] :until_block_num Ends the stream at the given block number. Default: nil.
|
92
|
+
def transactions(options = {}, &block)
|
93
|
+
blocks(options) do |block, block_num|
|
94
|
+
if block.nil?
|
95
|
+
warn "Batch missing block_num: #{block_num}, retrying ..."
|
96
|
+
|
97
|
+
block = block_api.get_block(block_num: block_num) do |result|
|
98
|
+
result.block
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
block.transactions.each_with_index do |transaction, index|
|
103
|
+
trx_id = block.transaction_ids[index]
|
104
|
+
|
105
|
+
yield transaction, trx_id, block_num
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns the latest operations from the blockchain.
|
111
|
+
#
|
112
|
+
# stream = Hive::Stream.new
|
113
|
+
# stream.operations do |op|
|
114
|
+
# puts op.to_json
|
115
|
+
# end
|
116
|
+
#
|
117
|
+
# If symbol are passed to `types` option, then only that operation is
|
118
|
+
# returned. Expected symbols are:
|
119
|
+
#
|
120
|
+
# account_create_operation
|
121
|
+
# account_create_with_delegation_operation
|
122
|
+
# account_update_operation
|
123
|
+
# account_witness_proxy_operation
|
124
|
+
# account_witness_vote_operation
|
125
|
+
# cancel_transfer_from_savings_operation
|
126
|
+
# change_recovery_account_operation
|
127
|
+
# claim_reward_balance_operation
|
128
|
+
# comment_operation
|
129
|
+
# comment_options_operation
|
130
|
+
# convert_operation
|
131
|
+
# custom_operation
|
132
|
+
# custom_json_operation
|
133
|
+
# decline_voting_rights_operation
|
134
|
+
# delegate_vesting_shares_operation
|
135
|
+
# delete_comment_operation
|
136
|
+
# escrow_approve_operation
|
137
|
+
# escrow_dispute_operation
|
138
|
+
# escrow_release_operation
|
139
|
+
# escrow_transfer_operation
|
140
|
+
# feed_publish_operation
|
141
|
+
# limit_order_cancel_operation
|
142
|
+
# limit_order_create_operation
|
143
|
+
# limit_order_create2_operation
|
144
|
+
# pow_operation
|
145
|
+
# pow2_operation
|
146
|
+
# recover_account_operation
|
147
|
+
# request_account_recovery_operation
|
148
|
+
# set_withdraw_vesting_route_operation
|
149
|
+
# transfer_operation
|
150
|
+
# transfer_from_savings_operation
|
151
|
+
# transfer_to_savings_operation
|
152
|
+
# transfer_to_vesting_operation
|
153
|
+
# vote_operation
|
154
|
+
# withdraw_vesting_operation
|
155
|
+
# witness_update_operation
|
156
|
+
#
|
157
|
+
# For example, to stream only votes:
|
158
|
+
#
|
159
|
+
# stream = Hive::Stream.new
|
160
|
+
# stream.operations(types: :vote_operation) do |vote|
|
161
|
+
# puts vote.to_json
|
162
|
+
# end
|
163
|
+
#
|
164
|
+
# ... Or ...
|
165
|
+
#
|
166
|
+
# stream = Hive::Stream.new
|
167
|
+
# stream.operations(:vote_operation) do |vote|
|
168
|
+
# puts vote.to_json
|
169
|
+
# end
|
170
|
+
#
|
171
|
+
# You can also stream virtual operations:
|
172
|
+
#
|
173
|
+
# stream = Hive::Stream.new
|
174
|
+
# stream.operations(types: :author_reward_operation, only_virtual: true) do |vop|
|
175
|
+
# v = vop.value
|
176
|
+
# puts "#{v.author} got paid for #{v.permlink}: #{[v.sbd_payout, v.steem_payout, v.vesting_payout]}"
|
177
|
+
# end
|
178
|
+
#
|
179
|
+
# ... or multiple virtual operation types;
|
180
|
+
#
|
181
|
+
# stream = Hive::Stream.new
|
182
|
+
# stream.operations(types: [:producer_reward_operation, :author_reward_operation], only_virtual: true) do |vop|
|
183
|
+
# puts vop.to_json
|
184
|
+
# end
|
185
|
+
#
|
186
|
+
# ... or all types, including virtual operation types from the head block number:
|
187
|
+
#
|
188
|
+
# stream = Hive::Stream.new(mode: :head)
|
189
|
+
# stream.operations(include_virtual: true) do |op|
|
190
|
+
# puts op.to_json
|
191
|
+
# end
|
192
|
+
#
|
193
|
+
# Expected virtual operation types:
|
194
|
+
#
|
195
|
+
# producer_reward_operation
|
196
|
+
# author_reward_operation
|
197
|
+
# curation_reward_operation
|
198
|
+
# fill_convert_request_operation
|
199
|
+
# fill_order_operation
|
200
|
+
# fill_vesting_withdraw_operation
|
201
|
+
# interest_operation
|
202
|
+
# shutdown_witness_operation
|
203
|
+
#
|
204
|
+
# @param args [Symbol || Array<Symbol> || Hash] the type(s) of operation or hash of expanded options, optional.
|
205
|
+
# @option args [Integer] :at_block_num Starts the stream at the given block number. Default: nil.
|
206
|
+
# @option args [Integer] :until_block_num Ends the stream at the given block number. Default: nil.
|
207
|
+
# @option args [Symbol || Array<Symbol>] :types the type(s) of operation, optional.
|
208
|
+
# @option args [Boolean] :only_virtual Only stream virtual options. Setting this true will improve performance because the stream only needs block numbers to then retrieve virtual operations. Default: false.
|
209
|
+
# @option args [Boolean] :include_virtual Also stream virtual options. Setting this true will impact performance. Default: false.
|
210
|
+
# @param block the block to execute for each result. Yields: |op, trx_id, block_num|
|
211
|
+
def operations(*args, &block)
|
212
|
+
options = {}
|
213
|
+
types = []
|
214
|
+
only_virtual = false
|
215
|
+
include_virtual = false
|
216
|
+
last_block_num = nil
|
217
|
+
|
218
|
+
case args.first
|
219
|
+
when Hash
|
220
|
+
options = args.first
|
221
|
+
types = transform_types(options[:types])
|
222
|
+
only_virtual = !!options[:only_virtual] || false
|
223
|
+
include_virtual = !!options[:include_virtual] || only_virtual || false
|
224
|
+
when Symbol, Array then types = transform_types(args)
|
225
|
+
end
|
226
|
+
|
227
|
+
if only_virtual
|
228
|
+
block_numbers(options) do |block_num|
|
229
|
+
get_virtual_ops(types, block_num, block)
|
230
|
+
end
|
231
|
+
else
|
232
|
+
transactions(options) do |transaction, trx_id, block_num|
|
233
|
+
transaction.operations.each do |op|
|
234
|
+
yield op, trx_id, block_num if types.none? || types.include?(op.type)
|
235
|
+
|
236
|
+
next unless last_block_num != block_num
|
237
|
+
|
238
|
+
last_block_num = block_num
|
239
|
+
|
240
|
+
get_virtual_ops(types, block_num, block) if include_virtual
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def account_history_api
|
247
|
+
@account_history_api ||= begin
|
248
|
+
Hive::AccountHistoryApi.new(@instance_options)
|
249
|
+
rescue Hive::UnknownApiError => e
|
250
|
+
warn "#{e.inspect}, falling back to Hive::CondenserApi." unless @no_warn
|
251
|
+
Hive::CondenserApi.new(@instance_options)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
private
|
255
|
+
# @private
|
256
|
+
def block_objects(options = {}, block)
|
257
|
+
object = options[:object]
|
258
|
+
object_method = "get_#{object}".to_sym
|
259
|
+
block_interval = BLOCK_INTERVAL
|
260
|
+
|
261
|
+
at_block_num, until_block_num = if !!block_range = options[:block_range]
|
262
|
+
[block_range.first, block_range.last]
|
263
|
+
else
|
264
|
+
[options[:at_block_num], options[:until_block_num]]
|
265
|
+
end
|
266
|
+
|
267
|
+
loop do
|
268
|
+
break if !!until_block_num && !!at_block_num && until_block_num < at_block_num
|
269
|
+
|
270
|
+
database_api.get_dynamic_global_properties do |properties|
|
271
|
+
current_block_num = find_block_number(properties)
|
272
|
+
current_block_num = [current_block_num, until_block_num].compact.min
|
273
|
+
at_block_num ||= current_block_num
|
274
|
+
|
275
|
+
if current_block_num >= at_block_num
|
276
|
+
range = at_block_num..current_block_num
|
277
|
+
|
278
|
+
if object == :block_numbers
|
279
|
+
range.each do |n|
|
280
|
+
block.call n
|
281
|
+
block_interval = BLOCK_INTERVAL
|
282
|
+
end
|
283
|
+
else
|
284
|
+
block_api.send(object_method, block_range: range) do |b, n|
|
285
|
+
block.call b, n
|
286
|
+
block_interval = BLOCK_INTERVAL
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
at_block_num = range.max + 1
|
291
|
+
else
|
292
|
+
# The stream has stalled, so let's back off and let the node sync
|
293
|
+
# up. We'll catch up with a bigger batch in the next cycle.
|
294
|
+
block_interval = [block_interval * 2, MAX_BACKOFF_BLOCK_INTERVAL].min
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
sleep block_interval
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
# @private
|
303
|
+
def find_block_number(properties)
|
304
|
+
block_num = case mode
|
305
|
+
when :head then properties.head_block_number
|
306
|
+
when :irreversible then properties.last_irreversible_block_num
|
307
|
+
else; raise Hive::ArgumentError, "Unknown mode: #{mode}"
|
308
|
+
end
|
309
|
+
|
310
|
+
block_num
|
311
|
+
end
|
312
|
+
|
313
|
+
# @private
|
314
|
+
def transform_types(types)
|
315
|
+
[types].compact.flatten.map do |type|
|
316
|
+
type = type.to_s
|
317
|
+
|
318
|
+
unless type.end_with? '_operation'
|
319
|
+
warn "Op type #{type} is deprecated. Use #{type}_operation instead." unless @no_warn
|
320
|
+
type += '_operation'
|
321
|
+
end
|
322
|
+
|
323
|
+
type
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# @private
|
328
|
+
def get_virtual_ops(types, block_num, block)
|
329
|
+
retries = 0
|
330
|
+
|
331
|
+
loop do
|
332
|
+
get_ops_in_block_options = case account_history_api
|
333
|
+
when Hive::CondenserApi
|
334
|
+
[block_num, true]
|
335
|
+
when Hive::AccountHistoryApi
|
336
|
+
{
|
337
|
+
block_num: block_num,
|
338
|
+
only_virtual: true
|
339
|
+
}
|
340
|
+
end
|
341
|
+
|
342
|
+
response = account_history_api.get_ops_in_block(*get_ops_in_block_options)
|
343
|
+
|
344
|
+
if response.nil? || (result = response.result).nil?
|
345
|
+
if retries < MAX_RETRY_COUNT
|
346
|
+
warn "Retrying get_ops_in_block on block #{block_num}" unless @no_warn
|
347
|
+
retries = retries + 1
|
348
|
+
sleep 9
|
349
|
+
redo
|
350
|
+
else
|
351
|
+
raise TooManyRetriesError, "unable to get valid result while finding virtual operations for block: #{block_num}"
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
ops = case account_history_api
|
356
|
+
when Hive::CondenserApi
|
357
|
+
result.map do |trx|
|
358
|
+
op = {type: trx.op[0] + '_operation', value: trx.op[1]}
|
359
|
+
op = Hashie::Mash.new(op)
|
360
|
+
end
|
361
|
+
when Hive::AccountHistoryApi then result.ops.map { |trx| trx.op }
|
362
|
+
end
|
363
|
+
|
364
|
+
if ops.empty?
|
365
|
+
if retries < MAX_RETRY_COUNT
|
366
|
+
sleep 3
|
367
|
+
retries = retries + 1
|
368
|
+
redo
|
369
|
+
else
|
370
|
+
warn "unable to find virtual operations for block: #{block_num}"
|
371
|
+
# raise TooManyRetriesError, "unable to find virtual operations for block: #{block_num}"
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
ops.each do |op|
|
376
|
+
next if types.any? && !types.include?(op.type)
|
377
|
+
|
378
|
+
block.call op, VOP_TRX_ID, block_num
|
379
|
+
end
|
380
|
+
|
381
|
+
break
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|