radiator 0.2.0a

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.
@@ -0,0 +1,113 @@
1
+ module Radiator
2
+
3
+ # See: https://github.com/steemit/steem-js/blob/766746adb5ded86380be982c844f4c269f7800ae/src/auth/serializer/src/operations.js
4
+ module OperationTypes
5
+ TYPES = {
6
+ transfer: {
7
+ amount: Type::Amount
8
+ },
9
+ transfer_to_vesting: {
10
+ amount: Type::Amount
11
+ },
12
+ withdraw_vesting: {
13
+ vesting_shares: Type::Amount
14
+ },
15
+ limit_order_create: {
16
+ orderid: Type::Uint32,
17
+ amount_to_sell: Type::Amount,
18
+ min_to_receive: Type::Amount,
19
+ expiration: Type::PointInTime
20
+ },
21
+ limit_order_cancel: {
22
+ orderid: Type::Uint32
23
+ },
24
+ convert: {
25
+ requestid: Type::Uint32,
26
+ amount: Type::Amount
27
+ },
28
+ account_create: {
29
+ fee: Type::Amount,
30
+ owner: Type::Permission,
31
+ active: Type::Permission,
32
+ posting: Type::Permission,
33
+ memo: Type::Permission
34
+ },
35
+ account_update: {
36
+ owner: Type::Permission,
37
+ active: Type::Permission,
38
+ posting: Type::Permission,
39
+ memo: Type::PublicKey
40
+ },
41
+ custom: {
42
+ id: Type::Uint16
43
+ },
44
+ comment_options: {
45
+ max_accepted_payout: Type::Amount
46
+ },
47
+ set_withdraw_vesting_route: {
48
+ percent: Type::Uint16
49
+ },
50
+ request_account_recovery: {
51
+ new_owner_Permission: Type::Permission
52
+ },
53
+ recover_account: {
54
+ new_owner_Permission: Type::Permission,
55
+ recent_owner_Permission: Type::Permission
56
+ },
57
+ escrow_transfer: {
58
+ sbd_amount: Type::Amount,
59
+ steem_amount: Type::Amount,
60
+ escrow_id: Type::Uint32,
61
+ fee: Type::Amount,
62
+ ratification_deadline: Type::PointInTime,
63
+ escrow_expiration: Type::PointInTime
64
+ },
65
+ escrow_dispute: {
66
+ escrow_id: Type::Uint32
67
+ },
68
+ escrow_release: {
69
+ escrow_id: Type::Uint32,
70
+ sbd_amount: Type::Amount,
71
+ steem_amount: Type::Amount
72
+ },
73
+ escrow_approve: {
74
+ escrow_id: Type::Uint32
75
+ },
76
+ transfer_to_savings: {
77
+ amount: Type::Amount
78
+ },
79
+ transfer_from_savings: {
80
+ request_id: Type::Uint32,
81
+ amount: Type::Amount
82
+ },
83
+ cancel_transfer_from_savings: {
84
+ request_id: Type::Uint32
85
+ },
86
+ reset_account: {
87
+ new_owner_Permission: Type::Amount
88
+ },
89
+ set_reset_account: {
90
+ reward_steem: Type::Amount,
91
+ reward_sbd: Type::Amount,
92
+ reward_vests: Type::Amount
93
+ },
94
+ claim_reward_balance: {
95
+ reward_steem: Type::Amount,
96
+ reward_sbd: Type::Amount,
97
+ reward_vests: Type::Amount
98
+ },
99
+ delegate_vesting_shares: {
100
+ vesting_shares: Type::Amount
101
+ }
102
+ }
103
+
104
+ def type(key, param, value)
105
+ return if value.nil?
106
+
107
+ t = TYPES[key] or return value
108
+ p = t[param] or return value
109
+
110
+ p.new(value)
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,260 @@
1
+ module Radiator
2
+ # Radiator::Stream allows a live view of the STEEM blockchain.
3
+ #
4
+ # All values returned by `get_dynamic_global_properties` can be streamed.
5
+ #
6
+ # For example, if you want to know which witness is currently signing blocks,
7
+ # use the following:
8
+ #
9
+ # stream = Radiator::Stream.new
10
+ # stream.current_witness do |witness|
11
+ # puts witness
12
+ # end
13
+ class Stream < Api
14
+ INITIAL_TIMEOUT = 0.0200
15
+ MAX_TIMEOUT = 80
16
+ MAX_BLOCKS_PER_NODE = 100
17
+
18
+ def initialize(options = {})
19
+ @api_options = options
20
+ @logger = @api_options[:logger] || Radiator.logger
21
+ end
22
+
23
+ def api
24
+ @api ||= Api.new(@api_options)
25
+ end
26
+
27
+ def method_names
28
+ @method_names ||= [
29
+ :head_block_number,
30
+ :head_block_id,
31
+ :time,
32
+ :current_witness,
33
+ :total_pow,
34
+ :num_pow_witnesses,
35
+ :virtual_supply,
36
+ :current_supply,
37
+ :confidential_supply,
38
+ :current_sbd_supply,
39
+ :confidential_sbd_supply,
40
+ :total_vesting_fund_steem,
41
+ :total_vesting_shares,
42
+ :total_reward_fund_steem,
43
+ :total_reward_shares2,
44
+ :total_activity_fund_steem,
45
+ :total_activity_fund_shares,
46
+ :sbd_interest_rate,
47
+ :average_block_size,
48
+ :maximum_block_size,
49
+ :current_aslot,
50
+ :recent_slots_filled,
51
+ :participation_count,
52
+ :last_irreversible_block_num,
53
+ :max_virtual_bandwidth,
54
+ :current_reserve_ratio,
55
+ :block_numbers,
56
+ :blocks
57
+ ].freeze
58
+ end
59
+
60
+ def method_params(method)
61
+ case method
62
+ when :block_numbers then {head_block_number: nil}
63
+ when :blocks then {get_block: :head_block_number}
64
+ else; nil
65
+ end
66
+ end
67
+
68
+ # Returns the latest operations from the blockchain.
69
+ #
70
+ # If symbol are passed, then only that operation is returned. Expected
71
+ # symbols are:
72
+ #
73
+ # transfer_to_vesting
74
+ # withdraw_vesting
75
+ # interest
76
+ # transfer
77
+ # liquidity_reward
78
+ # author_reward
79
+ # curation_reward
80
+ # transfer_to_savings
81
+ # transfer_from_savings
82
+ # cancel_transfer_from_savings
83
+ # escrow_transfer
84
+ # escrow_approve
85
+ # escrow_dispute
86
+ # escrow_release
87
+ # comment
88
+ # limit_order_create
89
+ # limit_order_cancel
90
+ # fill_convert_request
91
+ # fill_order
92
+ # vote
93
+ # account_witness_vote
94
+ # account_witness_proxy
95
+ # account_create
96
+ # account_update
97
+ # witness_update
98
+ # pow
99
+ # custom
100
+ #
101
+ # @param type [symbol || Array<symbol>] the type(s) of operation, optional.
102
+ # @param start starting block
103
+ # @param mode we have the choice between
104
+ # * :head the last block
105
+ # * :irreversible the block that is confirmed by 2/3 of all block producers and is thus irreversible!
106
+ # @param block the block to execute for each result, optional.
107
+ # @return [Hash]
108
+ def operations(type = nil, start = nil, mode = :irreversible, &block)
109
+ transactions(start, mode) do |transaction|
110
+ ops = transaction.operations.map do |t, op|
111
+ t = t.to_sym
112
+ if type == t
113
+ op
114
+ elsif type.nil? || [type].flatten.include?(t)
115
+ {t => op}
116
+ end
117
+ end.compact
118
+
119
+ next if ops.none?
120
+
121
+ return ops unless !!block
122
+
123
+ ops.each do |op|
124
+ yield op
125
+ end
126
+ end
127
+ end
128
+
129
+ # Returns the latest transactions from the blockchain.
130
+ #
131
+ # @param start starting block
132
+ # @param mode we have the choice between
133
+ # * :head the last block
134
+ # * :irreversible the block that is confirmed by 2/3 of all block producers and is thus irreversible!
135
+ # @param block the block to execute for each result, optional.
136
+ # @return [Hash]
137
+ def transactions(start = nil, mode = :irreversible, &block)
138
+ blocks(start, mode) do |b|
139
+ next if (_transactions = b.transactions).nil?
140
+ return _transactions unless !!block
141
+
142
+ _transactions.each.each do |transaction|
143
+ yield transaction
144
+ end
145
+ end
146
+ end
147
+
148
+ # Returns the latest blocks from the blockchain.
149
+ #
150
+ # @param start starting block
151
+ # @param mode we have the choice between
152
+ # * :head the last block
153
+ # * :irreversible the block that is confirmed by 2/3 of all block producers and is thus irreversible!
154
+ # * :max_blocks_per_node the number of blocks to read before trying a new node
155
+ # @param block the block to execute for each result, optional.
156
+ # @return [Hash]
157
+ def blocks(start = nil, mode = :irreversible, max_blocks_per_node = MAX_BLOCKS_PER_NODE, &block)
158
+ counter = 0
159
+
160
+ if start.nil?
161
+ properties = api.get_dynamic_global_properties.result
162
+ start = case mode.to_sym
163
+ when :head then properties.head_block_number
164
+ when :irreversible then properties.last_irreversible_block_num
165
+ else; raise StreamError, '"mode" has to be "head" or "irreversible"'
166
+ end
167
+ end
168
+
169
+ loop do
170
+ properties = api.get_dynamic_global_properties.result
171
+
172
+ head_block = case mode.to_sym
173
+ when :head then properties.head_block_number
174
+ when :irreversible then properties.last_irreversible_block_num
175
+ else; raise StreamError, '"mode" has to be "head" or "irreversible"'
176
+ end
177
+
178
+ [*(start..(head_block))].each do |n|
179
+ if (counter += 1) > max_blocks_per_node
180
+ shutdown
181
+ counter = 0
182
+ end
183
+
184
+ response = api.send(:get_block, n)
185
+ raise StreamError, JSON[response.error] if !!response.error
186
+ result = response.result
187
+
188
+ if !!block
189
+ yield result
190
+ else
191
+ return result
192
+ end
193
+ end
194
+
195
+ start = head_block + 1
196
+ sleep 3
197
+ end
198
+ end
199
+
200
+ def method_missing(m, *args, &block)
201
+ super unless respond_to_missing?(m)
202
+
203
+ @latest_values ||= []
204
+ @latest_values.shift(5) if @latest_values.size > 20
205
+ loop do
206
+ value = if (n = method_params(m)).nil?
207
+ key_value = api.get_dynamic_global_properties.result[m]
208
+ else
209
+ key = n.keys.first
210
+ if !!n[key]
211
+ r = api.get_dynamic_global_properties.result
212
+ key_value = param = r[n[key]]
213
+ result = nil
214
+ loop do
215
+ response = api.send(key, param)
216
+ raise StreamError, JSON[response.error] if !!response.error
217
+ result = response.result
218
+ break if !!result
219
+ @logger.warn "#{key}: #{param} result missing, retrying with timeout: #{@timeout || INITIAL_TIMEOUT} seconds"
220
+ shutdown
221
+ sleep timeout
222
+ end
223
+ @timeout = INITIAL_TIMEOUT
224
+ result
225
+ else
226
+ key_value = api.get_dynamic_global_properties.result[key]
227
+ end
228
+ end
229
+ unless @latest_values.include? key_value
230
+ @latest_values << key_value
231
+ if !!block
232
+ yield value
233
+ else
234
+ return value
235
+ end
236
+ end
237
+ sleep 0.0200
238
+ end
239
+ end
240
+
241
+ def timeout
242
+ @timeout ||= INITIAL_TIMEOUT
243
+ @timeout *= 2
244
+ @timeout = INITIAL_TIMEOUT if @timeout > MAX_TIMEOUT
245
+ @timeout
246
+ end
247
+
248
+ # Stops the persistant http connections.
249
+ #
250
+ def shutdown
251
+ begin
252
+ @api.shutdown
253
+ rescue => e
254
+ @logger.warn("Unable to shut down: #{e}")
255
+ end
256
+
257
+ @api = nil
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,11 @@
1
+ module Radiator
2
+ class TagApi < Api
3
+ def method_names
4
+ @method_names ||= [:get_tags].freeze
5
+ end
6
+
7
+ def api_name
8
+ :tag_api
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,150 @@
1
+ require 'bitcoin'
2
+ require 'digest'
3
+ require 'time'
4
+
5
+ module Radiator
6
+ # * graphenej:
7
+ # * https://github.com/kenCode-de/graphenej/blob/master/graphenej/src/main/java/de/bitsharesmunich/graphenej/Transaction.java#L142
8
+ class Transaction
9
+ include ChainConfig
10
+ include Utils
11
+
12
+ VALID_OPTIONS = %w(
13
+ wif private_key ref_block_num ref_block_prefix expiration operations
14
+ chain
15
+ ).map(&:to_sym)
16
+ VALID_OPTIONS.each { |option| attr_accessor option }
17
+
18
+ def initialize(options = {})
19
+ options.each do |k, v|
20
+ k = k.to_sym
21
+ if VALID_OPTIONS.include?(k.to_sym)
22
+ options.delete(k)
23
+ send("#{k}=", v)
24
+ end
25
+ end
26
+
27
+ @logger = options[:logger] || Radiator.logger
28
+ @chain ||= :steem
29
+ @chain_id = chain_id options[:chain_id]
30
+ @url = options[:url] || url
31
+ @operations ||= []
32
+
33
+ unless NETWORK_CHAIN_IDS.include? @chain_id
34
+ @logger.warn "Unknown chain id: #{@chain_id}"
35
+ end
36
+
37
+ if !!wif && !!private_key
38
+ raise TransactionError, "Do not pass both wif and private_key. That's confusing."
39
+ end
40
+
41
+ if !!wif
42
+ @private_key = Bitcoin::Key.from_base58 wif
43
+ end
44
+
45
+ options = options.merge(url: @url)
46
+ @api = Api.new(options)
47
+ @network_broadcast_api = NetworkBroadcastApi.new(options)
48
+ end
49
+
50
+ def chain_id(chain_id = nil)
51
+ return chain_id if !!chain_id
52
+
53
+ case chain.to_s.downcase.to_sym
54
+ when :steem then NETWORKS_STEEM_CHAIN_ID
55
+ when :golos then NETWORKS_GOLOS_CHAIN_ID
56
+ when :test then NETWORKS_TEST_CHAIN_ID
57
+ end
58
+ end
59
+
60
+ def url
61
+ case chain.to_s.downcase.to_sym
62
+ when :steem then NETWORKS_STEEM_DEFAULT_NODE
63
+ when :golos then NETWORKS_GOLOS_DEFAULT_NODE
64
+ when :test then NETWORKS_TEST_DEFAULT_NODE
65
+ end
66
+ end
67
+
68
+ def process(broadcast = false)
69
+ prepare
70
+
71
+ if broadcast
72
+ @network_broadcast_api.broadcast_transaction_synchronous(payload)
73
+ else
74
+ self
75
+ end
76
+ end
77
+ private
78
+ def payload
79
+ {
80
+ expiration: @expiration.strftime('%Y-%m-%dT%H:%M:%S'),
81
+ ref_block_num: @ref_block_num,
82
+ ref_block_prefix: @ref_block_prefix,
83
+ operations: @operations.map { |op| op.payload },
84
+ extensions: [],
85
+ signatures: [hexlify(signature)]
86
+ }
87
+ end
88
+
89
+ def prepare
90
+ raise TransactionError, "No wif or private key." unless !!@wif || !!@private_key
91
+
92
+ @properties = @api.get_dynamic_global_properties.result
93
+ @ref_block_num = @properties.head_block_number & 0xFFFF
94
+ @ref_block_prefix = unhexlify(@properties.head_block_id[8..-1]).unpack('V*')[0]
95
+
96
+ # The expiration allows for transactions to expire if they are not
97
+ # included into a block by that time. Always update it to the current
98
+ # time + EXPIRE_IN_SECS.
99
+ @expiration = Time.parse(@properties.time + 'Z') + EXPIRE_IN_SECS
100
+
101
+ self
102
+ end
103
+
104
+ def to_bytes
105
+ bytes = unhexlify(@chain_id)
106
+ bytes << pakS(@ref_block_num)
107
+ bytes << pakI(@ref_block_prefix)
108
+ bytes << pakI(@expiration.to_i)
109
+ bytes << pakC(@operations.size)
110
+
111
+ @operations.each do |op|
112
+ bytes << op.to_bytes
113
+ end
114
+
115
+ bytes << 0x00 # extensions
116
+
117
+ bytes
118
+ end
119
+
120
+ def digest
121
+ Digest::SHA256.digest(to_bytes)
122
+ end
123
+
124
+ def signature
125
+ public_key_hex = @private_key.pub
126
+ ec = Bitcoin::OpenSSL_EC
127
+ digest_hex = digest.freeze
128
+
129
+ loop do
130
+ @expiration += 1
131
+ sig = ec.sign_compact(digest_hex, @private_key.priv, public_key_hex)
132
+
133
+ next if public_key_hex != ec.recover_compact(digest_hex, sig)
134
+
135
+ return sig if canonical? sig
136
+ end
137
+ end
138
+
139
+ def canonical?(sig)
140
+ sig = sig.unpack('C*')
141
+
142
+ !(
143
+ ((sig[0] & 0x80 ) != 0) || ( sig[0] == 0 ) ||
144
+ ((sig[1] & 0x80 ) != 0) ||
145
+ ((sig[32] & 0x80 ) != 0) || ( sig[32] == 0 ) ||
146
+ ((sig[33] & 0x80 ) != 0)
147
+ )
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,36 @@
1
+ module Radiator
2
+ module Type
3
+
4
+ # See: https://github.com/xeroc/piston-lib/blob/34a7525cee119ec9b24a99577ede2d54466fca0e/steembase/operations.py
5
+ class Amount
6
+ def initialize(value)
7
+ @amount, @asset = value.strip.split(' ')
8
+ @precision = case @asset
9
+ when 'STEEM' then 3
10
+ when 'VESTS' then 6
11
+ when 'SBD' then 3
12
+ when 'GOLOS' then 3
13
+ when 'GESTS' then 6
14
+ when 'GBG' then 3
15
+ when 'CORE' then 3
16
+ when 'CESTS' then 6
17
+ when 'TEST' then 3
18
+ else; raise TypeError, "Asset #{@asset} unknown."
19
+ end
20
+ end
21
+
22
+ def to_bytes
23
+ asset = @asset.ljust(7, "\x00")
24
+ amount = (@amount.to_f * 10 ** @precision).round
25
+
26
+ [amount].pack('q') +
27
+ [@precision].pack('c') +
28
+ asset
29
+ end
30
+
31
+ def to_s
32
+ "#{@amount} #{@asset}"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,17 @@
1
+ module Radiator
2
+ module Type
3
+
4
+ # See: https://github.com/xeroc/piston-lib/blob/34a7525cee119ec9b24a99577ede2d54466fca0e/steembase/operations.py
5
+ class Permission
6
+ def initialize()
7
+ raise NotImplementedError, 'stub'
8
+ end
9
+
10
+ def to_bytes
11
+ end
12
+
13
+ def to_s
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ module Radiator
2
+ module Type
3
+
4
+ # See: https://github.com/xeroc/python-graphenelib/blob/98de98e219264d45fe04b3c28f3aabd1a9f58b71/graphenebase/types.py
5
+ class PointInTime
6
+ def initialize(value)
7
+ @value = value
8
+ end
9
+
10
+ def to_bytes
11
+ [Time.parse(@value + 'Z').to_i].pack('I')
12
+ end
13
+
14
+ def to_s
15
+ @value
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ module Radiator
2
+ module Type
3
+
4
+ # See: https://github.com/xeroc/piston-lib/blob/34a7525cee119ec9b24a99577ede2d54466fca0e/steembase/operations.py
5
+ class PublicKey
6
+ def initialize()
7
+ raise NotImplementedError, 'stub'
8
+ end
9
+
10
+ def to_bytes
11
+ end
12
+
13
+ def to_s
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ module Radiator
2
+ module Type
3
+
4
+ # See: https://github.com/xeroc/piston-lib/blob/34a7525cee119ec9b24a99577ede2d54466fca0e/steembase/operations.py
5
+ class Uint16
6
+ def initialize(value)
7
+ @value = value.to_i
8
+ end
9
+
10
+ def to_bytes
11
+ [@value].pack('S')
12
+ end
13
+
14
+ def to_s
15
+ @value.to_s
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Radiator
2
+ module Type
3
+
4
+ # See: https://github.com/xeroc/piston-lib/blob/34a7525cee119ec9b24a99577ede2d54466fca0e/steembase/operations.py
5
+ class Uint32
6
+ def initialize(value)
7
+ @value = value.to_i
8
+ end
9
+
10
+ def to_bytes
11
+ [@value].pack('L')
12
+ end
13
+
14
+ def to_s
15
+ @value.to_s
16
+ end
17
+ end
18
+ end
19
+ end