steem-ruby 0.9.1 → 0.9.6
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 +5 -5
- data/.gitignore +1 -1
- data/README.md +83 -10
- data/Rakefile +128 -31
- data/lib/steem.rb +49 -0
- data/lib/steem/api.rb +38 -36
- data/lib/steem/base_error.rb +32 -11
- data/lib/steem/block_api.rb +23 -3
- data/lib/steem/broadcast.rb +165 -2
- data/lib/steem/marshal.rb +231 -0
- data/lib/steem/mixins/jsonable.rb +37 -0
- data/lib/steem/mixins/retriable.rb +22 -13
- data/lib/steem/mixins/serializable.rb +45 -0
- data/lib/steem/operation.rb +141 -0
- data/lib/steem/operation/account_create.rb +10 -0
- data/lib/steem/operation/account_create_with_delegation.rb +12 -0
- data/lib/steem/operation/account_update.rb +8 -0
- data/lib/steem/operation/account_witness_proxy.rb +4 -0
- data/lib/steem/operation/account_witness_vote.rb +5 -0
- data/lib/steem/operation/cancel_transfer_from_savings.rb +4 -0
- data/lib/steem/operation/challenge_authority.rb +5 -0
- data/lib/steem/operation/change_recovery_account.rb +5 -0
- data/lib/steem/operation/claim_account.rb +5 -0
- data/lib/steem/operation/claim_reward_balance.rb +6 -0
- data/lib/steem/operation/comment.rb +9 -0
- data/lib/steem/operation/comment_options.rb +10 -0
- data/lib/steem/operation/convert.rb +5 -0
- data/lib/steem/operation/create_claimed_account.rb +10 -0
- data/lib/steem/operation/custom.rb +5 -0
- data/lib/steem/operation/custom_binary.rb +8 -0
- data/lib/steem/operation/custom_json.rb +6 -0
- data/lib/steem/operation/decline_voting_rights.rb +4 -0
- data/lib/steem/operation/delegate_vesting_shares.rb +5 -0
- data/lib/steem/operation/delete_comment.rb +4 -0
- data/lib/steem/operation/escrow_approve.rb +8 -0
- data/lib/steem/operation/escrow_dispute.rb +7 -0
- data/lib/steem/operation/escrow_release.rb +10 -0
- data/lib/steem/operation/escrow_transfer.rb +12 -0
- data/lib/steem/operation/feed_publish.rb +4 -0
- data/lib/steem/operation/limit_order_cancel.rb +4 -0
- data/lib/steem/operation/limit_order_create.rb +8 -0
- data/lib/steem/operation/limit_order_create2.rb +8 -0
- data/lib/steem/operation/prove_authority.rb +4 -0
- data/lib/steem/operation/recover_account.rb +6 -0
- data/lib/steem/operation/report_over_production.rb +5 -0
- data/lib/steem/operation/request_account_recovery.rb +6 -0
- data/lib/steem/operation/reset_account.rb +5 -0
- data/lib/steem/operation/set_reset_account.rb +5 -0
- data/lib/steem/operation/set_withdraw_vesting_route.rb +6 -0
- data/lib/steem/operation/transfer.rb +6 -0
- data/lib/steem/operation/transfer_from_savings.rb +7 -0
- data/lib/steem/operation/transfer_to_savings.rb +6 -0
- data/lib/steem/operation/transfer_to_vesting.rb +5 -0
- data/lib/steem/operation/vote.rb +6 -0
- data/lib/steem/operation/withdraw_vesting.rb +4 -0
- data/lib/steem/operation/witness_set_properties.rb +5 -0
- data/lib/steem/operation/witness_update.rb +7 -0
- data/lib/steem/rpc/base_client.rb +14 -2
- data/lib/steem/rpc/http_client.rb +17 -1
- data/lib/steem/stream.rb +385 -0
- data/lib/steem/transaction.rb +96 -0
- data/lib/steem/transaction_builder.rb +77 -70
- data/lib/steem/type/amount.rb +6 -0
- data/lib/steem/version.rb +1 -1
- data/steem-ruby.gemspec +9 -4
- metadata +203 -56
- data/Gemfile.lock +0 -73
data/lib/steem/base_error.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
module Steem
|
2
2
|
class BaseError < StandardError
|
3
|
-
def initialize(error, cause = nil)
|
3
|
+
def initialize(error = nil, cause = nil)
|
4
4
|
@error = error
|
5
5
|
@cause = cause
|
6
6
|
end
|
7
7
|
|
8
8
|
def to_s
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
detail = {}
|
10
|
+
detail[:error] = @error if !!@error
|
11
|
+
detail[:cause] = @cause if !!@cause
|
12
|
+
|
13
|
+
JSON[detail] rescue detail.to_s
|
14
14
|
end
|
15
15
|
|
16
16
|
def self.build_error(error, context)
|
@@ -19,7 +19,11 @@ module Steem
|
|
19
19
|
end
|
20
20
|
|
21
21
|
if error.message.include? 'Internal Error'
|
22
|
-
raise Steem::RemoteNodeError
|
22
|
+
raise Steem::RemoteNodeError, error.message, build_backtrace(error)
|
23
|
+
end
|
24
|
+
|
25
|
+
if error.message.include? 'Server error'
|
26
|
+
raise Steem::RemoteNodeError, error.message, build_backtrace(error)
|
23
27
|
end
|
24
28
|
|
25
29
|
if error.message.include? 'plugin not enabled'
|
@@ -30,6 +34,10 @@ module Steem
|
|
30
34
|
raise Steem::ArgumentError, "#{context}: #{error.message}", build_backtrace(error)
|
31
35
|
end
|
32
36
|
|
37
|
+
if error.message.include? 'Invalid params'
|
38
|
+
raise Steem::ArgumentError, "#{context}: #{error.message}", build_backtrace(error)
|
39
|
+
end
|
40
|
+
|
33
41
|
if error.message.start_with? 'Bad Cast:'
|
34
42
|
raise Steem::ArgumentError, "#{context}: #{error.message}", build_backtrace(error)
|
35
43
|
end
|
@@ -110,6 +118,10 @@ module Steem
|
|
110
118
|
raise Steem::MissingOtherAuthorityError, "#{context}: #{error.message}", build_backtrace(error)
|
111
119
|
end
|
112
120
|
|
121
|
+
if error.message.include? 'Upstream response error'
|
122
|
+
raise Steem::UpstreamResponseError, "#{context}: #{error.message}", build_backtrace(error)
|
123
|
+
end
|
124
|
+
|
113
125
|
if error.message.include? 'Bad or missing upstream response'
|
114
126
|
raise Steem::BadOrMissingUpstreamResponseError, "#{context}: #{error.message}", build_backtrace(error)
|
115
127
|
end
|
@@ -122,6 +134,10 @@ module Steem
|
|
122
134
|
raise Steem::InvalidAccountError, "#{context}: #{error.message}", build_backtrace(error)
|
123
135
|
end
|
124
136
|
|
137
|
+
if error.message.include?('Method') && error.message.include?(' does not exist.')
|
138
|
+
raise Steem::UnknownMethodError, "#{context}: #{error.message}", build_backtrace(error)
|
139
|
+
end
|
140
|
+
|
125
141
|
if error.message.include? 'Invalid operation name'
|
126
142
|
raise Steem::UnknownOperationError, "#{context}: #{error.message}", build_backtrace(error)
|
127
143
|
end
|
@@ -160,11 +176,10 @@ module Steem
|
|
160
176
|
end
|
161
177
|
end
|
162
178
|
|
179
|
+
class DeserializationError < BaseError; end
|
180
|
+
class SerializationMismatchError < BaseError; end
|
163
181
|
class UnsupportedChainError < BaseError; end
|
164
182
|
class ArgumentError < BaseError; end
|
165
|
-
class RemoteNodeError < BaseError; end
|
166
|
-
class RemoteDatabaseLockError < RemoteNodeError; end
|
167
|
-
class PluginNotEnabledError < RemoteNodeError; end
|
168
183
|
class TypeError < BaseError; end
|
169
184
|
class EmptyTransactionError < ArgumentError; end
|
170
185
|
class InvalidAccountError < ArgumentError; end
|
@@ -186,12 +201,18 @@ module Steem
|
|
186
201
|
class MissingOtherAuthorityError < MissingAuthorityError; end
|
187
202
|
class IncorrectRequestIdError < BaseError; end
|
188
203
|
class IncorrectResponseIdError < BaseError; end
|
189
|
-
class
|
204
|
+
class RemoteNodeError < BaseError; end
|
205
|
+
class UpstreamResponseError < RemoteNodeError; end
|
206
|
+
class RemoteDatabaseLockError < UpstreamResponseError; end
|
207
|
+
class PluginNotEnabledError < UpstreamResponseError; end
|
208
|
+
class BadOrMissingUpstreamResponseError < UpstreamResponseError; end
|
190
209
|
class TransactionIndexDisabledError < BaseError; end
|
191
210
|
class NotAppBaseError < BaseError; end
|
192
211
|
class UnknownApiError < BaseError; end
|
212
|
+
class UnknownMethodError < BaseError; end
|
193
213
|
class UnknownOperationError < BaseError; end
|
194
214
|
class JsonRpcBatchMaximumSizeExceededError < BaseError; end
|
195
215
|
class TooManyTimeoutsError < BaseError; end
|
216
|
+
class TooManyRetriesError < BaseError; end
|
196
217
|
class UnknownError < BaseError; end
|
197
218
|
end
|
data/lib/steem/block_api.rb
CHANGED
@@ -12,11 +12,25 @@ module Steem
|
|
12
12
|
super
|
13
13
|
end
|
14
14
|
|
15
|
+
# Uses a batched requst on a range of block headers.
|
16
|
+
#
|
17
|
+
# @param options [Hash] The attributes to get a block range with.
|
18
|
+
# @option options [Range] :block_range starting on one block number and ending on an higher block number.
|
19
|
+
def get_block_headers(options = {block_range: (0..0)}, &block)
|
20
|
+
get_block_objects(options.merge(object: :block_header), block)
|
21
|
+
end
|
22
|
+
|
15
23
|
# Uses a batched requst on a range of blocks.
|
16
24
|
#
|
17
25
|
# @param options [Hash] The attributes to get a block range with.
|
18
26
|
# @option options [Range] :block_range starting on one block number and ending on an higher block number.
|
19
27
|
def get_blocks(options = {block_range: (0..0)}, &block)
|
28
|
+
get_block_objects(options.merge(object: :block), block)
|
29
|
+
end
|
30
|
+
private
|
31
|
+
def get_block_objects(options = {block_range: (0..0)}, block = nil)
|
32
|
+
object = options[:object]
|
33
|
+
object_method = "get_#{object}".to_sym
|
20
34
|
block_range = options[:block_range] || (0..0)
|
21
35
|
|
22
36
|
if (start = block_range.first) < 1
|
@@ -33,15 +47,21 @@ module Steem
|
|
33
47
|
request_object = []
|
34
48
|
|
35
49
|
for i in sub_range do
|
36
|
-
@rpc_client.put(self.class.api_name,
|
50
|
+
@rpc_client.put(self.class.api_name, object_method, block_num: i, request_object: request_object)
|
37
51
|
end
|
38
52
|
|
39
53
|
if !!block
|
40
54
|
index = 0
|
41
55
|
@rpc_client.rpc_batch_execute(request_object: request_object) do |result, error, id|
|
42
56
|
block_num = sub_range.to_a[index]
|
43
|
-
index = index
|
44
|
-
|
57
|
+
index = index + 1
|
58
|
+
|
59
|
+
case object
|
60
|
+
when :block_header
|
61
|
+
block.call(result.nil? ? nil : result[:header], block_num)
|
62
|
+
else
|
63
|
+
block.call(result.nil? ? nil : result[object], block_num)
|
64
|
+
end
|
45
65
|
end
|
46
66
|
else
|
47
67
|
blocks = []
|
data/lib/steem/broadcast.rb
CHANGED
@@ -31,6 +31,7 @@ module Steem
|
|
31
31
|
# For details on what to pass to these methods, check out the {https://developers.steem.io/apidefinitions/broadcast-ops Steem Developer Portal Broadcast Operations} page.
|
32
32
|
class Broadcast
|
33
33
|
extend Retriable
|
34
|
+
extend Utils
|
34
35
|
|
35
36
|
DEFAULT_MAX_ACCEPTED_PAYOUT = Type::Amount.new(amount: '1000000000', precision: 3, nai: '@@000000013')
|
36
37
|
|
@@ -196,13 +197,17 @@ module Steem
|
|
196
197
|
permlink: params[:permlink],
|
197
198
|
max_accepted_payout: max_accepted_payout,
|
198
199
|
percent_steem_dollars: params[:percent_steem_dollars] || 10000,
|
200
|
+
# allow_replies: allow_replies,
|
199
201
|
allow_votes: allow_votes,
|
200
202
|
allow_curation_rewards: allow_curation_rewards,
|
201
203
|
extensions: []
|
202
204
|
}
|
203
205
|
|
204
206
|
if !!params[:beneficiaries]
|
205
|
-
comment_options[:extensions] << [
|
207
|
+
comment_options[:extensions] << [
|
208
|
+
comment_options[:extensions].size,
|
209
|
+
normalize_beneficiaries(options.merge(beneficiaries: params[:beneficiaries]))
|
210
|
+
]
|
206
211
|
end
|
207
212
|
|
208
213
|
ops << [:comment_options, comment_options]
|
@@ -509,6 +514,68 @@ module Steem
|
|
509
514
|
process(options.merge(ops: ops), &block)
|
510
515
|
end
|
511
516
|
|
517
|
+
# Create a claimed account.
|
518
|
+
# options = {
|
519
|
+
# wif: wif,
|
520
|
+
# params: {
|
521
|
+
# creator: creator_account_name,
|
522
|
+
# new_account_name: new_account_name,
|
523
|
+
# owner: {
|
524
|
+
# weight_threshold: 1,
|
525
|
+
# account_auths: [],
|
526
|
+
# key_auths: [[owner_public_key, 1]],
|
527
|
+
# },
|
528
|
+
# active: {
|
529
|
+
# weight_threshold: 1,
|
530
|
+
# account_auths: [],
|
531
|
+
# key_auths: [[active_public_key, 1]],
|
532
|
+
# },
|
533
|
+
# posting: {
|
534
|
+
# weight_threshold: 1,
|
535
|
+
# account_auths: [],
|
536
|
+
# key_auths: [[posting_public_key, 1]],
|
537
|
+
# },
|
538
|
+
# memo_key: memo_public_key,
|
539
|
+
# json_metadata: '{}'
|
540
|
+
# }
|
541
|
+
# }
|
542
|
+
#
|
543
|
+
# Steem::Broadcast.create_claimed_account(options)
|
544
|
+
#
|
545
|
+
# @param options [Hash] options
|
546
|
+
# @option options [String] :wif Active wif
|
547
|
+
# @option options [Hash] :params
|
548
|
+
# * :creator (String)
|
549
|
+
# * :new_account_name (String)
|
550
|
+
# * :owner (Hash)
|
551
|
+
# * :active (Hash)
|
552
|
+
# * :posting (Hash)
|
553
|
+
# * :memo_key (String)
|
554
|
+
# * :metadata (Hash) Metadata of the account, becomes `json_metadata`.
|
555
|
+
# * :json_metadata (String) String version of `metadata` (use one or the other).
|
556
|
+
# * :extensions (Array) (optional)
|
557
|
+
# @option options [Boolean] :pretend Just validate, do not broadcast.
|
558
|
+
# @see https://developers.steem.io/apidefinitions/broadcast-ops#broadcast_ops_create_claimed_account
|
559
|
+
def self.create_claimed_account(options, &block)
|
560
|
+
required_fields = %i(creator new_account_name owner active posting memo_key json_metadata)
|
561
|
+
params = options[:params]
|
562
|
+
|
563
|
+
if !!params[:metadata] && !!params[:json_metadata]
|
564
|
+
raise Steem::ArgumentError, 'Assign either metadata or json_metadata, not both.'
|
565
|
+
end
|
566
|
+
|
567
|
+
metadata = params.delete(:metadata) || {}
|
568
|
+
metadata ||= (JSON[params[:json_metadata]] || nil) || {}
|
569
|
+
params[:json_metadata] = metadata.to_json
|
570
|
+
|
571
|
+
check_required_fields(params, *required_fields)
|
572
|
+
|
573
|
+
params[:extensions] ||= []
|
574
|
+
ops = [[:create_claimed_account, params]]
|
575
|
+
|
576
|
+
process(options.merge(ops: ops), &block)
|
577
|
+
end
|
578
|
+
|
512
579
|
# Update an account.
|
513
580
|
# options = {
|
514
581
|
# wif: wif,
|
@@ -611,6 +678,73 @@ module Steem
|
|
611
678
|
process(options.merge(ops: ops), &block)
|
612
679
|
end
|
613
680
|
|
681
|
+
# Extensible replacement for #witness_update that supports additional
|
682
|
+
# properties added since HF20 and beyond.
|
683
|
+
#
|
684
|
+
# options = {
|
685
|
+
# wif: wif,
|
686
|
+
# params: {
|
687
|
+
# owner: witness_account_name,
|
688
|
+
# props: {
|
689
|
+
# account_creation_fee: '0.000 STEEM',
|
690
|
+
# maximum_block_size: 131072,
|
691
|
+
# sbd_interest_rate: 1000,
|
692
|
+
# account_subsidy_budget: 50000,
|
693
|
+
# account_subsidy_decay: 330782,
|
694
|
+
# sbd_exchange_rate: '1.000 STEEM',
|
695
|
+
# url: "https://steemit.com",
|
696
|
+
# new_signing_key: 'STM8LoQjQqJHvotqBo7HjnqmUbFW9oJ2theyqonzUd9DdJ7YYHsvD'
|
697
|
+
# }
|
698
|
+
# }
|
699
|
+
# }
|
700
|
+
#
|
701
|
+
# Steem::Broadcast.witness_set_properties(options)
|
702
|
+
#
|
703
|
+
# @param options [Hash] options
|
704
|
+
# @option options [String] :wif Active wif
|
705
|
+
# @option options [Hash] :params
|
706
|
+
# * :owner (String)
|
707
|
+
# * :props (String)
|
708
|
+
# @option options [Boolean] :pretend Just validate, do not broadcast.
|
709
|
+
# @see https://developers.steem.io/apidefinitions/broadcast-ops#broadcast_ops_witness_set_properties
|
710
|
+
# @see https://github.com/steemit/steem/blob/master/doc/witness_parameters.md
|
711
|
+
def self.witness_set_properties(options, &block)
|
712
|
+
required_fields = %i(owner props)
|
713
|
+
params = options[:params]
|
714
|
+
check_required_fields(params, *required_fields)
|
715
|
+
|
716
|
+
if !!(account_creation_fee = params[:props][:account_creation_fee] rescue nil)
|
717
|
+
params[:props][:account_creation_fee] = normalize_amount(options.merge amount: account_creation_fee, serialize: true)
|
718
|
+
end
|
719
|
+
|
720
|
+
if !!(sbd_exchange_rate = params[:props][:sbd_exchange_rate] rescue nil)
|
721
|
+
params[:props][:sbd_exchange_rate][:base] = normalize_amount(options.merge amount: sbd_exchange_rate[:base], serialize: true)
|
722
|
+
params[:props][:sbd_exchange_rate][:quote] = normalize_amount(options.merge amount: sbd_exchange_rate[:quote], serialize: true)
|
723
|
+
params[:props][:sbd_exchange_rate] = params[:props][:sbd_exchange_rate].to_json
|
724
|
+
end
|
725
|
+
|
726
|
+
%i(key new_signing_key).each do |key|
|
727
|
+
if !!params[key] && params[key].size == 53
|
728
|
+
params[key] = params[key][3..-1]
|
729
|
+
end
|
730
|
+
end
|
731
|
+
|
732
|
+
%i(account_creation_fee sbd_exchange_rate url new_signing_key).each do |key|
|
733
|
+
next unless !!params[:props][key]
|
734
|
+
|
735
|
+
val = params[:props][key].to_s
|
736
|
+
|
737
|
+
params[:props][key] = hexlify val unless val =~ /^[0-9A-F]+$/i
|
738
|
+
end
|
739
|
+
|
740
|
+
params[:props] = params[:props].to_a
|
741
|
+
|
742
|
+
params[:extensions] ||= []
|
743
|
+
ops = [[:witness_set_properties, params]]
|
744
|
+
|
745
|
+
process(options.merge(ops: ops), &block)
|
746
|
+
end
|
747
|
+
|
614
748
|
# All accounts with a VFS (Vesting Fund Shares) can vote for or against any
|
615
749
|
# witness.
|
616
750
|
#
|
@@ -1084,6 +1218,28 @@ module Steem
|
|
1084
1218
|
process(options.merge(ops: ops), &block)
|
1085
1219
|
end
|
1086
1220
|
|
1221
|
+
# @param options [Hash] options
|
1222
|
+
# @option options [String] :wif Active wif
|
1223
|
+
# @option options [Hash] :params
|
1224
|
+
# * :creator (String)
|
1225
|
+
# * :fee (String)
|
1226
|
+
# * :extensions (Array)
|
1227
|
+
# @option options [Boolean] :pretend Just validate, do not broadcast.
|
1228
|
+
# @see https://developers.steem.io/apidefinitions/broadcast-ops#broadcast_ops_claim_account
|
1229
|
+
def self.claim_account(options, &block)
|
1230
|
+
required_fields = %i(creator fee)
|
1231
|
+
params = options[:params]
|
1232
|
+
|
1233
|
+
check_required_fields(params, *required_fields)
|
1234
|
+
|
1235
|
+
params[:fee] = normalize_amount(options.merge amount: params[:fee])
|
1236
|
+
params[:extensions] ||= []
|
1237
|
+
|
1238
|
+
ops = [[:claim_account, params]]
|
1239
|
+
|
1240
|
+
process(options.merge(ops: ops), &block)
|
1241
|
+
end
|
1242
|
+
|
1087
1243
|
# @param options [Hash] options
|
1088
1244
|
# @option options [Array<Array<Hash>] :ops Operations to process.
|
1089
1245
|
# @option options [Boolean] :pretend Just validate, do not broadcast.
|
@@ -1131,12 +1287,19 @@ module Steem
|
|
1131
1287
|
def self.normalize_amount(options)
|
1132
1288
|
if !!options[:app_base]
|
1133
1289
|
Type::Amount.to_h(options[:amount])
|
1290
|
+
elsif !!options[:serialize]
|
1291
|
+
Type::Amount.to_s(options[:amount])
|
1134
1292
|
else
|
1135
1293
|
Type::Amount.to_s(options[:amount])
|
1136
1294
|
end
|
1137
1295
|
end
|
1138
1296
|
|
1139
|
-
|
1297
|
+
def self.normalize_beneficiaries(options)
|
1298
|
+
# Type::Beneficiaries.new(options[:beneficiaries])
|
1299
|
+
{beneficiaries: options[:beneficiaries]}
|
1300
|
+
end
|
1301
|
+
|
1302
|
+
# @private
|
1140
1303
|
def self.database_api(options)
|
1141
1304
|
options[:database_api] ||= if !!options[:app_base]
|
1142
1305
|
Steem::DatabaseApi.new(options)
|
@@ -0,0 +1,231 @@
|
|
1
|
+
require 'bindata'
|
2
|
+
require 'base58'
|
3
|
+
|
4
|
+
module Steem
|
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] || :steem
|
21
|
+
@prefix ||= case @chain
|
22
|
+
when :steem then NETWORKS_STEEM_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
|
+
Steem::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 Steem::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"] = Steem::Type::Amount.new(rate["base"])
|
168
|
+
rate["quote"] = Steem::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 = "Steem::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
|