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,36 @@
1
+ module Crea
2
+ module ChainConfig
3
+ EXPIRE_IN_SECS = 600
4
+ EXPIRE_IN_SECS_PROPOSAL = 24 * 60 * 60
5
+
6
+ NETWORKS_CREA_CHAIN_ID = '0000000000000000000000000000000000000000000000000000000000000000'
7
+ NETWORKS_CREA_ADDRESS_PREFIX = 'CREA'
8
+ NETWORKS_CREA_CORE_ASSET = ["0", 3, "@@000000021"] # CREA
9
+ NETWORKS_CREA_DEBT_ASSET = ["0", 3, "@@000000013"] # CBD
10
+ NETWORKS_CREA_VEST_ASSET = ["0", 6, "@@000000037"] # VESTS
11
+ NETWORKS_CREA_DEFAULT_NODE = 'https://node1.creary.net' # √
12
+ # NETWORKS_CREA_DEFAULT_NODE = 'https://api.crearystage.com' # √
13
+ # NETWORKS_CREA_DEFAULT_NODE = 'https://api.crearydev.com' # √
14
+ # NETWORKS_CREA_DEFAULT_NODE = 'https://appbasetest.timcliff.com'
15
+ # NETWORKS_CREA_DEFAULT_NODE = 'https://gtg.crea.house:8090'
16
+ # NETWORKS_CREA_DEFAULT_NODE = 'https://api.crea.house' # √?
17
+ # NETWORKS_CREA_DEFAULT_NODE = 'https://seed.bitcoiner.me'
18
+ # NETWORKS_CREA_DEFAULT_NODE = 'https://cread.minnowsupportproject.org'
19
+ # NETWORKS_CREA_DEFAULT_NODE = 'https://cread.privex.io'
20
+ # NETWORKS_CREA_DEFAULT_NODE = 'https://rpc.crealiberator.com'
21
+ # NETWORKS_CREA_DEFAULT_NODE = 'https://rpc.curiecrea.com'
22
+ # NETWORKS_CREA_DEFAULT_NODE = 'https://rpc.buildteam.io'
23
+ # NETWORKS_CREA_DEFAULT_NODE = 'https://cread.pevo.science'
24
+ # NETWORKS_CREA_DEFAULT_NODE = 'https://rpc.creaviz.com'
25
+ # NETWORKS_CREA_DEFAULT_NODE = 'https://cread.creagigs.org'
26
+
27
+ NETWORKS_TEST_CHAIN_ID = '46d82ab7d8db682eb1959aed0ada039a6d49afa1602491f93dde9cac3e8e6c32'
28
+ NETWORKS_TEST_ADDRESS_PREFIX = 'TST'
29
+ NETWORKS_TEST_CORE_ASSET = ["0", 3, "@@000000021"] # TESTS
30
+ NETWORKS_TEST_DEBT_ASSET = ["0", 3, "@@000000013"] # TBD
31
+ NETWORKS_TEST_VEST_ASSET = ["0", 6, "@@000000037"] # VESTS
32
+ NETWORKS_TEST_DEFAULT_NODE = 'https://testnet.crearydev.com'
33
+
34
+ NETWORK_CHAIN_IDS = [NETWORKS_CREA_CHAIN_ID, NETWORKS_TEST_CHAIN_ID]
35
+ end
36
+ end
@@ -0,0 +1,14 @@
1
+ module Crea
2
+ class Formatter
3
+ def self.reputation(raw)
4
+ raw = raw.to_i
5
+ neg = raw < 0
6
+ level = Math.log10(raw.abs)
7
+ level = [level - 9, 0].max
8
+ level = (neg ? -1 : 1) * level
9
+ level = (level * 9) + 25
10
+
11
+ level.round(1)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,108 @@
1
+ module Crea
2
+ # {Jsonrpc} allows you to inspect the available methods offered by a node.
3
+ # If a node runs a plugin you want, then all of the API methods it can exposes
4
+ # will automatically be available. This API is used internally to determine
5
+ # which APIs and methods are available on the node you specify.
6
+ #
7
+ # In theory, if a new plugin is created and enabled by the node, it will be
8
+ # available by this library without needing an update to its code.
9
+ class Jsonrpc < Api
10
+ API_METHODS = %i(get_signature get_methods)
11
+
12
+ def self.api_methods
13
+ @api_methods ||= {}
14
+ end
15
+
16
+ # Might help diagnose a cluster that has asymmetric plugin definitions.
17
+ def self.reset_api_methods
18
+ @api_methods = nil
19
+ end
20
+
21
+ def initialize(options = {})
22
+ @api_name = self.class.api_name = :jsonrpc
23
+ @methods = API_METHODS
24
+ super
25
+ end
26
+
27
+ def get_api_methods(&block)
28
+ api_methods = self.class.api_methods[@rpc_client.uri.to_s]
29
+
30
+ if api_methods.nil?
31
+ get_methods do |result, error, rpc_id|
32
+ raise NotAppBaseError, "#{@rpc_client.uri} does not appear to run AppBase" unless defined? result.map
33
+
34
+ methods = result.map do |method|
35
+ method.split('.').map(&:to_sym)
36
+ end
37
+
38
+ api_methods = Hashie::Mash.new
39
+
40
+ methods.each do |api, method|
41
+ api_methods[api] ||= []
42
+ api_methods[api] << method
43
+ end
44
+
45
+ self.class.api_methods[@rpc_client.uri.to_s] = api_methods
46
+ end
47
+ end
48
+
49
+ if !!block
50
+ api_methods.each do |api, methods|
51
+ yield api, methods
52
+ end
53
+ else
54
+ return api_methods
55
+ end
56
+ end
57
+
58
+ def get_all_signatures(&block)
59
+ request_object = []
60
+ method_names = []
61
+ method_map = {}
62
+ signatures = {}
63
+ offset = 0
64
+
65
+ get_api_methods do |api, methods|
66
+ request_object += methods.map do |method|
67
+ method_name = "#{api}.#{method}"
68
+ method_names << method_name
69
+ current_rpc_id = @rpc_client.rpc_id
70
+ offset += 1
71
+ method_map[current_rpc_id] = [api, method]
72
+
73
+ {
74
+ jsonrpc: '2.0',
75
+ id: current_rpc_id,
76
+ method: 'jsonrpc.get_signature',
77
+ params: {method: method_name}
78
+ }
79
+ end
80
+ end
81
+
82
+ chunks = if request_object.size > Crea::RPC::HttpClient::JSON_RPC_BATCH_SIZE_MAXIMUM
83
+ request_object.each_slice(Crea::RPC::HttpClient::JSON_RPC_BATCH_SIZE_MAXIMUM)
84
+ else
85
+ request_object
86
+ end
87
+
88
+ for request_object in chunks do
89
+ @rpc_client.rpc_batch_execute(request_object: request_object) do |result, error, id|
90
+ api, method = method_map[id]
91
+ api = api.to_sym
92
+ method = method.to_sym
93
+
94
+ signatures[api] ||= {}
95
+ signatures[api][method] = result
96
+ end
97
+
98
+ if !!block
99
+ signatures.each do |api, methods|
100
+ yield api, methods
101
+ end
102
+ end
103
+ end
104
+
105
+ return signatures unless !!block
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,231 @@
1
+ require 'bindata'
2
+ require 'base58'
3
+
4
+ module Crea
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] || :crea
21
+ @prefix ||= case @chain
22
+ when :crea then NETWORKS_CREA_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
+ Crea::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
+ cbd_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 Crea::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 :cbd_exchange_rate
166
+ JSON[string].tap do |rate|
167
+ rate["base"] = Crea::Type::Amount.new(rate["base"])
168
+ rate["quote"] = Crea::Type::Amount.new(rate["quote"])
169
+ end
170
+ when :cbd_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 = "Crea::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 Crea
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 Crea
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