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.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +54 -0
  3. data/CONTRIBUTING.md +79 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +91 -0
  6. data/LICENSE +21 -0
  7. data/README.md +248 -0
  8. data/Rakefile +358 -0
  9. data/gource.sh +6 -0
  10. data/hive-ruby.gemspec +40 -0
  11. data/images/Anthony Martin.png +0 -0
  12. data/lib/hive.rb +89 -0
  13. data/lib/hive/api.rb +223 -0
  14. data/lib/hive/base_error.rb +218 -0
  15. data/lib/hive/block_api.rb +78 -0
  16. data/lib/hive/bridge.rb +12 -0
  17. data/lib/hive/broadcast.rb +1334 -0
  18. data/lib/hive/chain_config.rb +34 -0
  19. data/lib/hive/fallback.rb +287 -0
  20. data/lib/hive/formatter.rb +14 -0
  21. data/lib/hive/jsonrpc.rb +112 -0
  22. data/lib/hive/marshal.rb +231 -0
  23. data/lib/hive/mixins/jsonable.rb +37 -0
  24. data/lib/hive/mixins/retriable.rb +58 -0
  25. data/lib/hive/mixins/serializable.rb +45 -0
  26. data/lib/hive/operation.rb +141 -0
  27. data/lib/hive/operation/account_create.rb +10 -0
  28. data/lib/hive/operation/account_create_with_delegation.rb +12 -0
  29. data/lib/hive/operation/account_update.rb +8 -0
  30. data/lib/hive/operation/account_witness_proxy.rb +4 -0
  31. data/lib/hive/operation/account_witness_vote.rb +5 -0
  32. data/lib/hive/operation/cancel_transfer_from_savings.rb +4 -0
  33. data/lib/hive/operation/challenge_authority.rb +5 -0
  34. data/lib/hive/operation/change_recovery_account.rb +5 -0
  35. data/lib/hive/operation/claim_account.rb +5 -0
  36. data/lib/hive/operation/claim_reward_balance.rb +6 -0
  37. data/lib/hive/operation/comment.rb +9 -0
  38. data/lib/hive/operation/comment_options.rb +10 -0
  39. data/lib/hive/operation/convert.rb +5 -0
  40. data/lib/hive/operation/create_claimed_account.rb +10 -0
  41. data/lib/hive/operation/custom.rb +5 -0
  42. data/lib/hive/operation/custom_binary.rb +8 -0
  43. data/lib/hive/operation/custom_json.rb +6 -0
  44. data/lib/hive/operation/decline_voting_rights.rb +4 -0
  45. data/lib/hive/operation/delegate_vesting_shares.rb +5 -0
  46. data/lib/hive/operation/delete_comment.rb +4 -0
  47. data/lib/hive/operation/escrow_approve.rb +8 -0
  48. data/lib/hive/operation/escrow_dispute.rb +7 -0
  49. data/lib/hive/operation/escrow_release.rb +10 -0
  50. data/lib/hive/operation/escrow_transfer.rb +12 -0
  51. data/lib/hive/operation/feed_publish.rb +4 -0
  52. data/lib/hive/operation/limit_order_cancel.rb +4 -0
  53. data/lib/hive/operation/limit_order_create.rb +8 -0
  54. data/lib/hive/operation/limit_order_create2.rb +8 -0
  55. data/lib/hive/operation/prove_authority.rb +4 -0
  56. data/lib/hive/operation/recover_account.rb +6 -0
  57. data/lib/hive/operation/report_over_production.rb +5 -0
  58. data/lib/hive/operation/request_account_recovery.rb +6 -0
  59. data/lib/hive/operation/reset_account.rb +5 -0
  60. data/lib/hive/operation/set_reset_account.rb +5 -0
  61. data/lib/hive/operation/set_withdraw_vesting_route.rb +6 -0
  62. data/lib/hive/operation/transfer.rb +6 -0
  63. data/lib/hive/operation/transfer_from_savings.rb +7 -0
  64. data/lib/hive/operation/transfer_to_savings.rb +6 -0
  65. data/lib/hive/operation/transfer_to_vesting.rb +5 -0
  66. data/lib/hive/operation/vote.rb +6 -0
  67. data/lib/hive/operation/withdraw_vesting.rb +4 -0
  68. data/lib/hive/operation/witness_set_properties.rb +5 -0
  69. data/lib/hive/operation/witness_update.rb +7 -0
  70. data/lib/hive/rpc/base_client.rb +179 -0
  71. data/lib/hive/rpc/http_client.rb +143 -0
  72. data/lib/hive/rpc/thread_safe_http_client.rb +35 -0
  73. data/lib/hive/stream.rb +385 -0
  74. data/lib/hive/transaction.rb +115 -0
  75. data/lib/hive/transaction_builder.rb +406 -0
  76. data/lib/hive/type/amount.rb +126 -0
  77. data/lib/hive/type/base_type.rb +10 -0
  78. data/lib/hive/utils.rb +17 -0
  79. data/lib/hive/version.rb +4 -0
  80. metadata +502 -0
@@ -0,0 +1,231 @@
1
+ require 'bindata'
2
+ require 'base58'
3
+
4
+ module Hive
5
+ class Marshal
6
+ include Utils
7
+ include ChainConfig
8
+
9
+ PUBLIC_KEY_DISABLED = '1111111111111111111111111111111114T1Anm'
10
+
11
+ attr_reader :bytes, :cursor
12
+
13
+ def initialize(options = {})
14
+ @bytes = if !!(hex = options[:hex])
15
+ unhexlify hex
16
+ else
17
+ options[:bytes]
18
+ end
19
+
20
+ @chain = options[:chain] || :hive
21
+ @prefix ||= case @chain
22
+ when :hive then NETWORKS_HIVE_ADDRESS_PREFIX
23
+ when :test then NETWORKS_TEST_ADDRESS_PREFIX
24
+ else; raise UnsupportedChainError, "Unsupported chain: #{@chain}"
25
+ end
26
+ @cursor = 0
27
+ end
28
+
29
+ def hex
30
+ hexlify bytes
31
+ end
32
+
33
+ def rewind!
34
+ @cursor = 0
35
+ end
36
+
37
+ def step(n = 0)
38
+ @cursor += n
39
+ end
40
+
41
+ def scan(len)
42
+ bytes.slice(@cursor..(@cursor - 1) + len).tap { |_| @cursor += len }
43
+ end
44
+
45
+ def operation_type
46
+ Operation::IDS[unsigned_char]
47
+ end
48
+
49
+ def unsigned_char; BinData::Uint8le.read(scan(1)); end # 8-bit unsigned
50
+ def uint16; BinData::Uint16le.read(scan(2)); end # 16-bit unsigned, VAX (little-endian) byte order
51
+ def uint32; BinData::Uint32le.read(scan(4)); end # 32-bit unsigned, VAX (little-endian) byte order
52
+ def uint64; BinData::Uint64le.read(scan(8)); end # 64-bit unsigned, little-endian
53
+
54
+ def signed_char; BinData::Int8le.read(scan(1)); end # 8-bit signed
55
+ def int16; BinData::Int16le.read(scan(2)); end # 16-bit signed, little-endian
56
+ def int32; BinData::Int32le.read(scan(4)); end # 32-bit signed, little-endian
57
+ def int64; BinData::Int64le.read(scan(8)); end # 64-bit signed, little-endian
58
+
59
+ def boolean; scan(1) == "\x01"; end
60
+
61
+ def varint
62
+ shift = 0
63
+ result = 0
64
+ bytes = []
65
+
66
+ while (n = unsigned_char) >> 7 == 1
67
+ bytes << n
68
+ end
69
+
70
+ bytes << n
71
+
72
+ bytes.each do |b|
73
+ result += ((b & 0x7f) << shift)
74
+ break unless (b & 0x80)
75
+ shift += 7
76
+ end
77
+
78
+ result
79
+ end
80
+
81
+ def string(len = nil); scan(len || varint); end
82
+
83
+ def raw_bytes(len = nil); scan(len || varint).force_encoding('BINARY'); end
84
+
85
+ def point_in_time
86
+ if (time = uint32) == 2**32-1
87
+ Time.at -1
88
+ else
89
+ Time.at time
90
+ end.utc
91
+ end
92
+
93
+ def public_key(prefix = @prefix)
94
+ raw_public_key = raw_bytes(33)
95
+ checksum = OpenSSL::Digest::RIPEMD160.digest(raw_public_key)
96
+ key = Base58.binary_to_base58(raw_public_key + checksum.slice(0, 4), :bitcoin)
97
+
98
+ prefix + key unless key == PUBLIC_KEY_DISABLED
99
+ end
100
+
101
+ def amount
102
+ amount = uint64.to_f
103
+ precision = signed_char
104
+ asset = scan(7).strip
105
+
106
+ amount = "%.#{precision}f #{asset}" % (amount / 10 ** precision)
107
+
108
+ Hive::Type::Amount.new(amount)
109
+ end
110
+
111
+ def price
112
+ {base: amount, quote: amount}
113
+ end
114
+
115
+ def authority(options = {optional: false})
116
+ return if !!options[:optional] && unsigned_char == 0
117
+
118
+ {
119
+ weight_threshold: uint32,
120
+ account_auths: varint.times.map { [string, uint16] },
121
+ key_auths: varint.times.map { [public_key, uint16] }
122
+ }
123
+ end
124
+
125
+ def optional_authority
126
+ authority(optional: true)
127
+ end
128
+
129
+ def comment_options_extensions
130
+ if scan(1) == "\x01"
131
+ beneficiaries
132
+ else
133
+ []
134
+ end
135
+ end
136
+
137
+ def beneficiaries
138
+ if scan(1) == "\x00"
139
+ varint.times.map {{account: string, weight: uint16}}
140
+ end
141
+ end
142
+
143
+ def chain_properties
144
+ {
145
+ account_creation_fee: amount,
146
+ maximum_block_size: uint32,
147
+ sbd_interest_rate: uint16
148
+ }
149
+ end
150
+
151
+ def required_auths
152
+ varint.times.map { string }
153
+ end
154
+
155
+ def witness_properties
156
+ properties = {}
157
+
158
+ varint.times do
159
+ key = string.to_sym
160
+ properties[key] = case key
161
+ when :account_creation_fee then Hive::Type::Amount.new(string)
162
+ when :account_subsidy_budget then scan(3)
163
+ when :account_subsidy_decay, :maximum_block_size then uint32
164
+ when :url then string
165
+ when :sbd_exchange_rate
166
+ JSON[string].tap do |rate|
167
+ rate["base"] = Hive::Type::Amount.new(rate["base"])
168
+ rate["quote"] = Hive::Type::Amount.new(rate["quote"])
169
+ end
170
+ when :sbd_interest_rate then uint16
171
+ when :key, :new_signing_key then @prefix + scan(50)
172
+ else; raise "Unknown witness property: #{key}"
173
+ end
174
+ end
175
+
176
+ properties
177
+ end
178
+
179
+ def empty_array
180
+ unsigned_char == 0 and [] or raise "Found non-empty array."
181
+ end
182
+
183
+ def transaction(options = {})
184
+ trx = options[:trx] || Transaction.new
185
+
186
+ trx.ref_block_num = uint16
187
+ trx.ref_block_prefix = uint32
188
+ trx.expiration = point_in_time
189
+
190
+ trx.operations = operations
191
+
192
+ trx
193
+ rescue => e
194
+ raise DeserializationError.new("Transaction failed\nOriginal serialized bytes:\n[#{hex[0..(@cursor * 2) - 1]}]#{hex[((@cursor) * 2)..-1]}", e)
195
+ end
196
+
197
+ def operations
198
+ operations_len = signed_char
199
+ operations = []
200
+
201
+ while operations.size < operations_len do
202
+ begin
203
+ type = operation_type
204
+ break if type.nil?
205
+
206
+ op_class_name = type.to_s.sub!(/_operation$/, '')
207
+ op_class_name = "Hive::Operation::" + op_class_name.split('_').map(&:capitalize).join
208
+ op_class = Object::const_get(op_class_name)
209
+ op = op_class.new
210
+
211
+ op_class::serializable_types.each do |k, v|
212
+ begin
213
+ # binding.pry if v == :comment_options_extensions
214
+ op.send("#{k}=", send(v))
215
+ rescue => e
216
+ raise DeserializationError.new("#{type}.#{k} (#{v}) failed", e)
217
+ end
218
+ end
219
+
220
+ operations << {type: type, value: op}
221
+ rescue => e
222
+ raise DeserializationError.new("#{type} failed", e)
223
+ end
224
+ end
225
+
226
+ operations
227
+ rescue => e
228
+ raise DeserializationError.new("Operations failed", e)
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,37 @@
1
+ module Hive
2
+ module JSONable
3
+ module ClassMethods
4
+ attr_accessor :attributes
5
+
6
+ def attr_accessor *attrs
7
+ self.attributes = Array attrs
8
+
9
+ super
10
+ end
11
+ end
12
+
13
+ def self.included(base)
14
+ base.extend(ClassMethods)
15
+ end
16
+
17
+ def as_json options = {}
18
+ serialized = Hash.new
19
+
20
+ self.class.attributes.each do |attribute|
21
+ unless (value = self.public_send attribute).nil?
22
+ serialized[attribute] = if value.respond_to? :strftime
23
+ value.strftime('%Y-%m-%dT%H:%M:%S')
24
+ else
25
+ value
26
+ end
27
+ end
28
+ end
29
+
30
+ serialized
31
+ end
32
+
33
+ def to_json *a
34
+ as_json.to_json *a
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,58 @@
1
+ module Hive
2
+ module Retriable
3
+ # @private
4
+ MAX_RETRY_COUNT = 30
5
+
6
+ MAX_RETRY_ELAPSE = 60
7
+
8
+ # @private
9
+ MAX_BACKOFF = MAX_RETRY_ELAPSE / 4
10
+
11
+ RETRYABLE_EXCEPTIONS = [
12
+ NonCanonicalSignatureError, IncorrectRequestIdError,
13
+ IncorrectResponseIdError, RemoteDatabaseLockError
14
+ ]
15
+
16
+ def can_retry?(e = nil)
17
+ @retry_count ||= 0
18
+
19
+ return false if @retry_count >= MAX_RETRY_COUNT
20
+
21
+ @retry_count = if retry_reset?
22
+ @first_retry_at = nil
23
+ else
24
+ @retry_count + 1
25
+ end
26
+
27
+ can_retry = case e
28
+ when *RETRYABLE_EXCEPTIONS then true
29
+ else; false
30
+ end
31
+
32
+ backoff if can_retry
33
+
34
+ can_retry
35
+ end
36
+ private
37
+ # @private
38
+ def first_retry_at
39
+ @first_retry_at ||= Time.now.utc
40
+ end
41
+
42
+ # @private
43
+ def retry_reset?
44
+ Time.now.utc - first_retry_at > MAX_RETRY_ELAPSE
45
+ end
46
+
47
+ # Expontential backoff.
48
+ #
49
+ # @private
50
+ def backoff
51
+ @backoff ||= 0.1
52
+ @backoff *= 2
53
+ @backoff = 0.1 if @backoff > MAX_BACKOFF
54
+
55
+ sleep @backoff
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,45 @@
1
+ module Hive
2
+ module Serializable
3
+ NUMERIC_TYPES = %i(unsigned_char uint16 uint32 uint64 signed_char int16 int32
4
+ int64 varint)
5
+
6
+ KNOWN_TYPES = NUMERIC_TYPES + %i(boolean string raw_bytes point_in_time
7
+ public_key amount price authority optional_authority
8
+ comment_options_extensions beneficiaries chain_properties required_auths
9
+ witness_properties empty_array lambda)
10
+
11
+ module ClassMethods
12
+ def def_attr key_pair
13
+ name = key_pair.keys.first.to_sym
14
+ type = key_pair.values.first.to_sym
15
+
16
+ self.attributes ||= []
17
+ self.attributes << name
18
+
19
+ attr_accessor *attributes
20
+ add_type name, type
21
+ end
22
+
23
+ def add_type name, type
24
+ name = name.to_sym
25
+ type = type.to_sym
26
+ raise "Unknown type: #{type}" unless KNOWN_TYPES.include? type
27
+
28
+ @serializable_types ||= {}
29
+ @serializable_types[name] = type
30
+ end
31
+
32
+ def numeric?(name)
33
+ NUMERIC_TYPES.include? @serializable_types[name.to_sym]
34
+ end
35
+
36
+ def serializable_types
37
+ @serializable_types
38
+ end
39
+ end
40
+
41
+ def self.included(base)
42
+ base.extend(ClassMethods)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,141 @@
1
+ module Hive
2
+ class Operation
3
+ include JSONable
4
+ include Serializable
5
+ include Utils
6
+
7
+ # IDs derrived from:
8
+ # https://github.com/openhive-network/hive/blob/127a441fbac2f06804359968bda83b66e602c891/libraries/protocol/include/steem/protocol/operations.hpp
9
+
10
+ IDS = [
11
+ :vote_operation,
12
+ :comment_operation,
13
+
14
+ :transfer_operation,
15
+ :transfer_to_vesting_operation,
16
+ :withdraw_vesting_operation,
17
+
18
+ :limit_order_create_operation,
19
+ :limit_order_cancel_operation,
20
+
21
+ :feed_publish_operation,
22
+ :convert_operation,
23
+
24
+ :account_create_operation,
25
+ :account_update_operation,
26
+
27
+ :witness_update_operation,
28
+ :account_witness_vote_operation,
29
+ :account_witness_proxy_operation,
30
+
31
+ :pow_operation,
32
+
33
+ :custom_operation,
34
+
35
+ :report_over_production_operation,
36
+
37
+ :delete_comment_operation,
38
+ :custom_json_operation,
39
+ :comment_options_operation,
40
+ :set_withdraw_vesting_route_operation,
41
+ :limit_order_create2_operation,
42
+ :claim_account_operation,
43
+ :create_claimed_account_operation,
44
+ :request_account_recovery_operation,
45
+ :recover_account_operation,
46
+ :change_recovery_account_operation,
47
+ :escrow_transfer_operation,
48
+ :escrow_dispute_operation,
49
+ :escrow_release_operation,
50
+ :pow2_operation,
51
+ :escrow_approve_operation,
52
+ :transfer_to_savings_operation,
53
+ :transfer_from_savings_operation,
54
+ :cancel_transfer_from_savings_operation,
55
+ :custom_binary_operation,
56
+ :decline_voting_rights_operation,
57
+ :reset_account_operation,
58
+ :set_reset_account_operation,
59
+ :claim_reward_balance_operation,
60
+ :delegate_vesting_shares_operation,
61
+ :account_create_with_delegation_operation,
62
+ :witness_set_properties_operation,
63
+
64
+ # SMT operations
65
+ :claim_reward_balance2_operation,
66
+
67
+ :smt_setup_operation,
68
+ :smt_cap_reveal_operation,
69
+ :smt_refund_operation,
70
+ :smt_setup_emissions_operation,
71
+ :smt_set_setup_parameters_operation,
72
+ :smt_set_runtime_parameters_operation,
73
+ :smt_create_operation,
74
+
75
+ # virtual operations below this point
76
+ :fill_convert_request_operation,
77
+ :author_reward_operation,
78
+ :curation_reward_operation,
79
+ :comment_reward_operation,
80
+ :liquidity_reward_operation,
81
+ :interest_operation,
82
+ :fill_vesting_withdraw_operation,
83
+ :fill_order_operation,
84
+ :shutdown_witness_operation,
85
+ :fill_transfer_from_savings_operation,
86
+ :hardfork_operation,
87
+ :comment_payout_update_operation,
88
+ :return_vesting_delegation_operation,
89
+ :comment_benefactor_reward_operation,
90
+ :producer_reward_operation,
91
+ :clear_null_account_balance_operation
92
+ ]
93
+
94
+ def self.op_id(op)
95
+ IDS.find_index op
96
+ end
97
+
98
+ def inspect
99
+ properties = self.class.attributes.map do |prop|
100
+ unless (v = instance_variable_get("@#{prop}")).nil?
101
+ v = if v.respond_to? :strftime
102
+ v.strftime('%Y-%m-%dT%H:%M:%S')
103
+ else
104
+ v
105
+ end
106
+
107
+ "@#{prop}=#{v}"
108
+ end
109
+ end.compact.join(', ')
110
+
111
+ "#<#{self.class.name} [#{properties}]>"
112
+ end
113
+
114
+ def [](key)
115
+ key = key.to_sym
116
+ send(key) if self.class.attributes.include?(key)
117
+ end
118
+
119
+ def []=(key, value)
120
+ key = key.to_sym
121
+
122
+ if self.class.attributes.include?(key)
123
+ if self.class.numeric? key
124
+ send("#{key}=", value.to_i)
125
+ else
126
+ send("#{key}=", value)
127
+ end
128
+ end
129
+ end
130
+
131
+ def ==(other_op)
132
+ return false if self.class != other_op.class
133
+
134
+ self.class.attributes.each do |prop|
135
+ return false if self[prop] != other_op[prop]
136
+ end
137
+
138
+ true
139
+ end
140
+ end
141
+ end