steem-ruby 0.9.0 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -1
  3. data/README.md +88 -15
  4. data/Rakefile +128 -31
  5. data/lib/steem.rb +49 -0
  6. data/lib/steem/api.rb +39 -37
  7. data/lib/steem/base_error.rb +80 -41
  8. data/lib/steem/block_api.rb +23 -3
  9. data/lib/steem/broadcast.rb +465 -29
  10. data/lib/steem/chain_config.rb +3 -3
  11. data/lib/steem/marshal.rb +231 -0
  12. data/lib/steem/mixins/jsonable.rb +37 -0
  13. data/lib/steem/mixins/retriable.rb +30 -24
  14. data/lib/steem/mixins/serializable.rb +45 -0
  15. data/lib/steem/operation.rb +141 -0
  16. data/lib/steem/operation/account_create.rb +10 -0
  17. data/lib/steem/operation/account_create_with_delegation.rb +12 -0
  18. data/lib/steem/operation/account_update.rb +8 -0
  19. data/lib/steem/operation/account_witness_proxy.rb +4 -0
  20. data/lib/steem/operation/account_witness_vote.rb +5 -0
  21. data/lib/steem/operation/cancel_transfer_from_savings.rb +4 -0
  22. data/lib/steem/operation/challenge_authority.rb +5 -0
  23. data/lib/steem/operation/change_recovery_account.rb +5 -0
  24. data/lib/steem/operation/claim_account.rb +5 -0
  25. data/lib/steem/operation/claim_reward_balance.rb +6 -0
  26. data/lib/steem/operation/comment.rb +9 -0
  27. data/lib/steem/operation/comment_options.rb +10 -0
  28. data/lib/steem/operation/convert.rb +5 -0
  29. data/lib/steem/operation/create_claimed_account.rb +10 -0
  30. data/lib/steem/operation/custom.rb +5 -0
  31. data/lib/steem/operation/custom_binary.rb +8 -0
  32. data/lib/steem/operation/custom_json.rb +6 -0
  33. data/lib/steem/operation/decline_voting_rights.rb +4 -0
  34. data/lib/steem/operation/delegate_vesting_shares.rb +5 -0
  35. data/lib/steem/operation/delete_comment.rb +4 -0
  36. data/lib/steem/operation/escrow_approve.rb +8 -0
  37. data/lib/steem/operation/escrow_dispute.rb +7 -0
  38. data/lib/steem/operation/escrow_release.rb +10 -0
  39. data/lib/steem/operation/escrow_transfer.rb +12 -0
  40. data/lib/steem/operation/feed_publish.rb +4 -0
  41. data/lib/steem/operation/limit_order_cancel.rb +4 -0
  42. data/lib/steem/operation/limit_order_create.rb +8 -0
  43. data/lib/steem/operation/limit_order_create2.rb +8 -0
  44. data/lib/steem/operation/prove_authority.rb +4 -0
  45. data/lib/steem/operation/recover_account.rb +6 -0
  46. data/lib/steem/operation/report_over_production.rb +5 -0
  47. data/lib/steem/operation/request_account_recovery.rb +6 -0
  48. data/lib/steem/operation/reset_account.rb +5 -0
  49. data/lib/steem/operation/set_reset_account.rb +5 -0
  50. data/lib/steem/operation/set_withdraw_vesting_route.rb +6 -0
  51. data/lib/steem/operation/transfer.rb +6 -0
  52. data/lib/steem/operation/transfer_from_savings.rb +7 -0
  53. data/lib/steem/operation/transfer_to_savings.rb +6 -0
  54. data/lib/steem/operation/transfer_to_vesting.rb +5 -0
  55. data/lib/steem/operation/vote.rb +6 -0
  56. data/lib/steem/operation/withdraw_vesting.rb +4 -0
  57. data/lib/steem/operation/witness_set_properties.rb +5 -0
  58. data/lib/steem/operation/witness_update.rb +7 -0
  59. data/lib/steem/rpc/base_client.rb +16 -4
  60. data/lib/steem/rpc/http_client.rb +18 -2
  61. data/lib/steem/stream.rb +385 -0
  62. data/lib/steem/transaction.rb +96 -0
  63. data/lib/steem/transaction_builder.rb +176 -103
  64. data/lib/steem/type/amount.rb +61 -9
  65. data/lib/steem/version.rb +1 -1
  66. data/steem-ruby.gemspec +9 -4
  67. metadata +203 -56
  68. data/Gemfile.lock +0 -73
@@ -0,0 +1,10 @@
1
+ class Steem::Operation::AccountCreate < Steem::Operation
2
+ def_attr fee: :amount
3
+ def_attr creator: :string
4
+ def_attr new_account_name: :string
5
+ def_attr owner: :authority
6
+ def_attr active: :authority
7
+ def_attr posting: :authority
8
+ def_attr memo_key: :public_key
9
+ def_attr json_metadata: :string
10
+ end
@@ -0,0 +1,12 @@
1
+ class Steem::Operation::AccountCreateWithDelegation < Steem::Operation
2
+ def_attr fee: :amount
3
+ def_attr delegation: :amount
4
+ def_attr creator: :string
5
+ def_attr new_account_name: :string
6
+ def_attr owner: :authority
7
+ def_attr active: :authority
8
+ def_attr posting: :authority
9
+ def_attr memo_key: :public_key
10
+ def_attr json_metadata: :string
11
+ def_attr extensions: :empty_array
12
+ end
@@ -0,0 +1,8 @@
1
+ class Steem::Operation::AccountUpdate < Steem::Operation
2
+ def_attr account: :string
3
+ def_attr owner: :optional_authority
4
+ def_attr active: :optional_authority
5
+ def_attr posting: :optional_authority
6
+ def_attr memo_key: :public_key
7
+ def_attr json_metadata: :string
8
+ end
@@ -0,0 +1,4 @@
1
+ class Steem::Operation::AccountWitnessProxy < Steem::Operation
2
+ def_attr account: :string
3
+ def_attr proxy: :string
4
+ end
@@ -0,0 +1,5 @@
1
+ class Steem::Operation::AccountWitnessVote < Steem::Operation
2
+ def_attr account: :string
3
+ def_attr witness: :string
4
+ def_attr approve: :boolean
5
+ end
@@ -0,0 +1,4 @@
1
+ class Steem::Operation::CancelTransferFromSavings < Steem::Operation
2
+ def_attr from: :string
3
+ def_attr request_id: :uint32
4
+ end
@@ -0,0 +1,5 @@
1
+ class Steem::Operation::ChallengeAuthority < Steem::Operation
2
+ def_attr challenger: :string
3
+ def_attr challenged: :string
4
+ def_attr require_owner: :boolean
5
+ end
@@ -0,0 +1,5 @@
1
+ class Steem::Operation::ChangeRecoveryAccount < Steem::Operation
2
+ def_attr account_to_recover: :string
3
+ def_attr new_recovery_account: :string
4
+ def_attr extensions: :empty_array
5
+ end
@@ -0,0 +1,5 @@
1
+ class Steem::Operation::ClaimAccount < Steem::Operation
2
+ def_attr creator: :string
3
+ def_attr fee: :amount
4
+ def_attr extensions: :empty_array
5
+ end
@@ -0,0 +1,6 @@
1
+ class Steem::Operation::ClaimRewardBalance < Steem::Operation
2
+ def_attr account: :string
3
+ def_attr reward_steem: :amount
4
+ def_attr reward_sbd: :amount
5
+ def_attr reward_vests: :amount
6
+ end
@@ -0,0 +1,9 @@
1
+ class Steem::Operation::Comment < Steem::Operation
2
+ def_attr parent_author: :string
3
+ def_attr parent_permlink: :string
4
+ def_attr author: :string
5
+ def_attr permlink: :string
6
+ def_attr title: :string
7
+ def_attr body: :string
8
+ def_attr json_metadata: :string
9
+ end
@@ -0,0 +1,10 @@
1
+ class Steem::Operation::CommentOptions < Steem::Operation
2
+ def_attr author: :string
3
+ def_attr permlink: :string
4
+ def_attr max_accepted_payout: :amount
5
+ def_attr percent_steem_dollars: :uint32
6
+ # def_attr allow_replies: :boolean
7
+ def_attr allow_votes: :boolean
8
+ def_attr allow_curation_rewards: :boolean
9
+ def_attr extensions: :comment_options_extensions
10
+ end
@@ -0,0 +1,5 @@
1
+ class Steem::Operation::Convert < Steem::Operation
2
+ def_attr owner: :string
3
+ def_attr requestid: :uint32
4
+ def_attr amount: :amount
5
+ end
@@ -0,0 +1,10 @@
1
+ class Steem::Operation::CreateClaimedAccount < Steem::Operation
2
+ def_attr creator: :string
3
+ def_attr new_account_name: :string
4
+ def_attr owner: :authority
5
+ def_attr active: :authority
6
+ def_attr posting: :authority
7
+ def_attr memo_key: :public_key
8
+ def_attr json_metadata: :string
9
+ def_attr extensions: :empty_array
10
+ end
@@ -0,0 +1,5 @@
1
+ class Steem::Operation::Custom < Steem::Operation
2
+ def_attr required_auths: :required_auths
3
+ def_attr id: :uint32
4
+ def_attr data: :raw_bytes
5
+ end
@@ -0,0 +1,8 @@
1
+ class Steem::Operation::CustomBinary < Steem::Operation
2
+ def_attr required_owner_auths: :required_auths
3
+ def_attr required_active_auths: :required_auths
4
+ def_attr required_posting_auths: :required_auths
5
+ def_attr required_auths: :required_auths
6
+ def_attr id: :string
7
+ def_attr data: :raw_bytes
8
+ end
@@ -0,0 +1,6 @@
1
+ class Steem::Operation::CustomJson < Steem::Operation
2
+ def_attr required_auths: :required_auths
3
+ def_attr required_posting_auths: :required_auths
4
+ def_attr id: :string
5
+ def_attr json: :string
6
+ end
@@ -0,0 +1,4 @@
1
+ class Steem::Operation::DeclineVotingRights < Steem::Operation
2
+ def_attr account: :string
3
+ def_attr decline: :boolean
4
+ end
@@ -0,0 +1,5 @@
1
+ class Steem::Operation::DelegateVestingShares < Steem::Operation
2
+ def_attr delegator: :string
3
+ def_attr delegatee: :string
4
+ def_attr vesting_shares: :amount
5
+ end
@@ -0,0 +1,4 @@
1
+ class Steem::Operation::DeleteComment < Steem::Operation
2
+ def_attr author: :string
3
+ def_attr permlink: :string
4
+ end
@@ -0,0 +1,8 @@
1
+ class Steem::Operation::EscrowApprove < Steem::Operation
2
+ def_attr from: :string
3
+ def_attr to: :string
4
+ def_attr agent: :string
5
+ def_attr who: :string
6
+ def_attr escrow_id: :uint32
7
+ def_attr approve: :boolean
8
+ end
@@ -0,0 +1,7 @@
1
+ class Steem::Operation::EscrowDispute < Steem::Operation
2
+ def_attr from: :string
3
+ def_attr to: :string
4
+ def_attr agent: :string
5
+ def_attr who: :string
6
+ def_attr escrow_id: :uint32
7
+ end
@@ -0,0 +1,10 @@
1
+ class Steem::Operation::EscrowRelease < Steem::Operation
2
+ def_attr from: :string
3
+ def_attr to: :string
4
+ def_attr agent: :string
5
+ def_attr who: :string
6
+ def_attr receiver: :string
7
+ def_attr escrow_id: :uint32
8
+ def_attr sbd_amount: :amount
9
+ def_attr steem_amount: :amount
10
+ end
@@ -0,0 +1,12 @@
1
+ class Steem::Operation::EscrowTransfer < Steem::Operation
2
+ def_attr from: :string
3
+ def_attr to: :string
4
+ def_attr sbd_amount: :amount
5
+ def_attr steem_amount: :amount
6
+ def_attr escrow_id: :uint32
7
+ def_attr agent: :string
8
+ def_attr fee: :amount
9
+ def_attr json_metadata: :string
10
+ def_attr ratification_deadline: :point_in_time
11
+ def_attr escrow_expiration: :point_in_time
12
+ end
@@ -0,0 +1,4 @@
1
+ class Steem::Operation::FeedPublish < Steem::Operation
2
+ def_attr publisher: :string
3
+ def_attr exchange_rate: :price
4
+ end
@@ -0,0 +1,4 @@
1
+ class Steem::Operation::LimitOrderCancel < Steem::Operation
2
+ def_attr owner: :string
3
+ def_attr orderid: :uint32
4
+ end
@@ -0,0 +1,8 @@
1
+ class Steem::Operation::LimitOrderCreate < Steem::Operation
2
+ def_attr owner: :string
3
+ def_attr orderid: :uint32
4
+ def_attr amount_to_sell: :amount
5
+ def_attr min_to_receive: :amount
6
+ def_attr fill_or_kill: :boolean
7
+ def_attr expiration: :point_in_time
8
+ end
@@ -0,0 +1,8 @@
1
+ class Steem::Operation::LimitOrderCreate2 < Steem::Operation
2
+ def_attr owner: :string
3
+ def_attr orderid: :uint32
4
+ def_attr amount_to_sell: :amount
5
+ def_attr fill_or_kill: :boolean
6
+ def_attr exchange_rate: :price
7
+ def_attr expiration: :point_in_time
8
+ end
@@ -0,0 +1,4 @@
1
+ class Steem::Operation::ProveAuthority < Steem::Operation
2
+ def_attr challenged: :string
3
+ def_attr require_owner: :boolean
4
+ end
@@ -0,0 +1,6 @@
1
+ class Steem::Operation::RecoverAccount < Steem::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 Steem::Operation::ReportOverProduction < Steem::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 Steem::Operation::RequestAccountRecovery < Steem::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 Steem::Operation::ResetAccount < Steem::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 Steem::Operation::SetResetAccount < Steem::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 Steem::Operation::SetWithdrawVestingRoute < Steem::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 Steem::Operation::Transfer < Steem::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 Steem::Operation::TransferFromSavings < Steem::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 Steem::Operation::TransferToSavings < Steem::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 Steem::Operation::TransferToVesting < Steem::Operation
2
+ def_attr from: :string
3
+ def_attr to: :string
4
+ def_attr amount: :amount
5
+ end
@@ -0,0 +1,6 @@
1
+ class Steem::Operation::Vote < Steem::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 Steem::Operation::WithdrawVesting < Steem::Operation
2
+ def_attr account: :string
3
+ def_attr vesting_shares: :amount
4
+ end
@@ -0,0 +1,5 @@
1
+ class Steem::Operation::WitnessSetProperties < Steem::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 Steem::Operation::WitnessUpdate < Steem::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
@@ -12,8 +12,7 @@ module Steem
12
12
  MAX_TIMEOUT_BACKOFF = 30
13
13
 
14
14
  # @private
15
- TIMEOUT_ERRORS = [Net::ReadTimeout, Errno::EBADF, Errno::ECONNREFUSED,
16
- IOError]
15
+ TIMEOUT_ERRORS = [Net::ReadTimeout, Errno::EBADF, IOError]
17
16
 
18
17
  def initialize(options = {})
19
18
  @chain = options[:chain] || :steem
@@ -118,7 +117,7 @@ module Steem
118
117
  error = response['error'].to_json if !!response['error']
119
118
 
120
119
  if req_id != res_id
121
- raise IncorrectResponseIdError, "#{method}: The json-rpc id did not match. Request was: #{req_id}, got: #{res_id.inspect}", error.nil? ? nil : error.to_json
120
+ raise IncorrectResponseIdError, "#{method}: The json-rpc id did not match. Request was: #{req_id}, got: #{res_id.inspect}", BaseError.send(:build_backtrace, error)
122
121
  end
123
122
  end
124
123
 
@@ -143,7 +142,7 @@ module Steem
143
142
  raise TooManyTimeoutsError.new("Too many timeouts for: #{context}", cause)
144
143
  elsif @timeout_retry_count % 10 == 0
145
144
  msg = "#{@timeout_retry_count} retry attempts for: #{context}"
146
- msg += "; cause: #{e}" if !!cause
145
+ msg += "; cause: #{cause}" if !!cause
147
146
  error_pipe.puts msg
148
147
  end
149
148
 
@@ -162,6 +161,19 @@ module Steem
162
161
 
163
162
  sleep @backoff
164
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 Steem::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
165
177
  end
166
178
  end
167
179
  end
@@ -17,7 +17,7 @@ module Steem
17
17
  #
18
18
  # @private
19
19
  TIMEOUT_ERRORS = [Net::OpenTimeout, JSON::ParserError, Net::ReadTimeout,
20
- Errno::EBADF, Errno::ECONNREFUSED, IOError]
20
+ Errno::EBADF, IOError, Errno::ENETDOWN, Steem::RemoteDatabaseLockError]
21
21
 
22
22
  # @private
23
23
  POST_HEADERS = {
@@ -30,7 +30,7 @@ module Steem
30
30
  def http
31
31
  @http ||= Net::HTTP.new(uri.host, uri.port).tap do |http|
32
32
  http.use_ssl = true if uri.to_s =~ /^https/i
33
- http.keep_alive_timeout = 2 # seconds
33
+ http.keep_alive_timeout = 150 # seconds
34
34
 
35
35
  # WARNING This method opens a serious security hole. Never use this
36
36
  # method in production code.
@@ -108,6 +108,22 @@ module Steem
108
108
  else; response
109
109
  end
110
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 Steem::ArgumentError, r.error.inspect
123
+ end
124
+ end
125
+ end
126
+
111
127
  yield_response response, &block
112
128
  when '504' # Gateway Timeout
113
129
  throw retry_timeout(:tota_cera_pila, response.body)
@@ -0,0 +1,385 @@
1
+ module Steem
2
+ # Steem::Stream allows a live view of the STEEM blockchain.
3
+ #
4
+ # Example streaming blocks:
5
+ #
6
+ # stream = Steem::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 = Steem::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 = Steem::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 [Steem::DatabaseApi] :database_api
41
+ # @option options [Steem::BlockApi] :block_api
42
+ # @option options [Steem::AccountHistoryApi || Steem::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] || Steem::DatabaseApi.new(options)
50
+ @block_api = options[:block_api] || Steem::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 {Steem::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 = Steem::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 = Steem::Stream.new
160
+ # stream.operations(types: :vote_operation) do |vote|
161
+ # puts vote.to_json
162
+ # end
163
+ #
164
+ # ... Or ...
165
+ #
166
+ # stream = Steem::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 = Steem::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 = Steem::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 = Steem::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
+ Steem::AccountHistoryApi.new(@instance_options)
249
+ rescue Steem::UnknownApiError => e
250
+ warn "#{e.inspect}, falling back to Steem::CondenserApi." unless @no_warn
251
+ Steem::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 Steem::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 Steem::CondenserApi
334
+ [block_num, true]
335
+ when Steem::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 Steem::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 Steem::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