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.
- checksums.yaml +7 -0
- data/.gitignore +54 -0
- data/CONTRIBUTING.md +79 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +91 -0
- data/LICENSE +21 -0
- data/README.md +248 -0
- data/Rakefile +358 -0
- data/gource.sh +6 -0
- data/hive-ruby.gemspec +40 -0
- data/images/Anthony Martin.png +0 -0
- data/lib/hive.rb +89 -0
- data/lib/hive/api.rb +223 -0
- data/lib/hive/base_error.rb +218 -0
- data/lib/hive/block_api.rb +78 -0
- data/lib/hive/bridge.rb +12 -0
- data/lib/hive/broadcast.rb +1334 -0
- data/lib/hive/chain_config.rb +34 -0
- data/lib/hive/fallback.rb +287 -0
- data/lib/hive/formatter.rb +14 -0
- data/lib/hive/jsonrpc.rb +112 -0
- data/lib/hive/marshal.rb +231 -0
- data/lib/hive/mixins/jsonable.rb +37 -0
- data/lib/hive/mixins/retriable.rb +58 -0
- data/lib/hive/mixins/serializable.rb +45 -0
- data/lib/hive/operation.rb +141 -0
- data/lib/hive/operation/account_create.rb +10 -0
- data/lib/hive/operation/account_create_with_delegation.rb +12 -0
- data/lib/hive/operation/account_update.rb +8 -0
- data/lib/hive/operation/account_witness_proxy.rb +4 -0
- data/lib/hive/operation/account_witness_vote.rb +5 -0
- data/lib/hive/operation/cancel_transfer_from_savings.rb +4 -0
- data/lib/hive/operation/challenge_authority.rb +5 -0
- data/lib/hive/operation/change_recovery_account.rb +5 -0
- data/lib/hive/operation/claim_account.rb +5 -0
- data/lib/hive/operation/claim_reward_balance.rb +6 -0
- data/lib/hive/operation/comment.rb +9 -0
- data/lib/hive/operation/comment_options.rb +10 -0
- data/lib/hive/operation/convert.rb +5 -0
- data/lib/hive/operation/create_claimed_account.rb +10 -0
- data/lib/hive/operation/custom.rb +5 -0
- data/lib/hive/operation/custom_binary.rb +8 -0
- data/lib/hive/operation/custom_json.rb +6 -0
- data/lib/hive/operation/decline_voting_rights.rb +4 -0
- data/lib/hive/operation/delegate_vesting_shares.rb +5 -0
- data/lib/hive/operation/delete_comment.rb +4 -0
- data/lib/hive/operation/escrow_approve.rb +8 -0
- data/lib/hive/operation/escrow_dispute.rb +7 -0
- data/lib/hive/operation/escrow_release.rb +10 -0
- data/lib/hive/operation/escrow_transfer.rb +12 -0
- data/lib/hive/operation/feed_publish.rb +4 -0
- data/lib/hive/operation/limit_order_cancel.rb +4 -0
- data/lib/hive/operation/limit_order_create.rb +8 -0
- data/lib/hive/operation/limit_order_create2.rb +8 -0
- data/lib/hive/operation/prove_authority.rb +4 -0
- data/lib/hive/operation/recover_account.rb +6 -0
- data/lib/hive/operation/report_over_production.rb +5 -0
- data/lib/hive/operation/request_account_recovery.rb +6 -0
- data/lib/hive/operation/reset_account.rb +5 -0
- data/lib/hive/operation/set_reset_account.rb +5 -0
- data/lib/hive/operation/set_withdraw_vesting_route.rb +6 -0
- data/lib/hive/operation/transfer.rb +6 -0
- data/lib/hive/operation/transfer_from_savings.rb +7 -0
- data/lib/hive/operation/transfer_to_savings.rb +6 -0
- data/lib/hive/operation/transfer_to_vesting.rb +5 -0
- data/lib/hive/operation/vote.rb +6 -0
- data/lib/hive/operation/withdraw_vesting.rb +4 -0
- data/lib/hive/operation/witness_set_properties.rb +5 -0
- data/lib/hive/operation/witness_update.rb +7 -0
- data/lib/hive/rpc/base_client.rb +179 -0
- data/lib/hive/rpc/http_client.rb +143 -0
- data/lib/hive/rpc/thread_safe_http_client.rb +35 -0
- data/lib/hive/stream.rb +385 -0
- data/lib/hive/transaction.rb +115 -0
- data/lib/hive/transaction_builder.rb +406 -0
- data/lib/hive/type/amount.rb +126 -0
- data/lib/hive/type/base_type.rb +10 -0
- data/lib/hive/utils.rb +17 -0
- data/lib/hive/version.rb +4 -0
- metadata +502 -0
@@ -0,0 +1,115 @@
|
|
1
|
+
module Hive
|
2
|
+
class Transaction
|
3
|
+
include JSONable
|
4
|
+
include Utils
|
5
|
+
|
6
|
+
ATTRIBUTES = %i(id ref_block_num ref_block_prefix expiration operations
|
7
|
+
extensions signatures)
|
8
|
+
|
9
|
+
attr_accessor *ATTRIBUTES
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
if !!(hex = options.delete(:hex))
|
13
|
+
marshal = Marshal.new(hex: hex)
|
14
|
+
marshal.transaction(trx: self)
|
15
|
+
end
|
16
|
+
|
17
|
+
options.each do |k, v|
|
18
|
+
raise Hive::ArgumentError, "Invalid option specified: #{k}" unless ATTRIBUTES.include?(k.to_sym)
|
19
|
+
|
20
|
+
send("#{k}=", v)
|
21
|
+
end
|
22
|
+
|
23
|
+
self.operations ||= []
|
24
|
+
self.extensions ||= []
|
25
|
+
self.signatures ||= []
|
26
|
+
|
27
|
+
self.expiration = case @expiration
|
28
|
+
when String then Time.parse(@expiration + 'Z')
|
29
|
+
else; @expiration
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def inspect
|
34
|
+
properties = ATTRIBUTES.map do |prop|
|
35
|
+
unless (v = instance_variable_get("@#{prop}")).nil?
|
36
|
+
v = if v.respond_to? :strftime
|
37
|
+
v.strftime('%Y-%m-%dT%H:%M:%S')
|
38
|
+
else
|
39
|
+
v
|
40
|
+
end
|
41
|
+
|
42
|
+
"@#{prop}=#{v}"
|
43
|
+
end
|
44
|
+
end.compact.join(', ')
|
45
|
+
|
46
|
+
"#<#{self.class.name} [#{properties}]>"
|
47
|
+
end
|
48
|
+
|
49
|
+
def expiration
|
50
|
+
if @expiration.respond_to? :strftime
|
51
|
+
@expiration.strftime('%Y-%m-%dT%H:%M:%S')
|
52
|
+
else
|
53
|
+
@expiration
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def expired?
|
58
|
+
@expiration.nil? || @expiration < Time.now
|
59
|
+
end
|
60
|
+
|
61
|
+
def [](key)
|
62
|
+
key = key.to_sym
|
63
|
+
send(key) if self.class.attributes.include?(key)
|
64
|
+
end
|
65
|
+
|
66
|
+
def []=(key, value)
|
67
|
+
key = key.to_sym
|
68
|
+
send("#{key}=", value) if self.class.attributes.include?(key)
|
69
|
+
end
|
70
|
+
|
71
|
+
def ==(other_trx)
|
72
|
+
return true if self.equal? other_trx
|
73
|
+
return false unless self.class == other_trx.class
|
74
|
+
|
75
|
+
begin
|
76
|
+
return false if self[:ref_block_num].to_i != transform_value(other_trx[:ref_block_num]).to_i
|
77
|
+
return false if self[:ref_block_prefix].to_i != transform_value(other_trx[:ref_block_prefix]).to_i
|
78
|
+
return false if self[:expiration].to_i != other_trx[:expiration].to_i
|
79
|
+
return false if self[:operations].size != other_trx[:operations].size
|
80
|
+
|
81
|
+
op_values = self[:operations].map do |type, value|
|
82
|
+
[type.to_s, value.values.map{|v| transform_value(v).gsub(/[^a-zA-Z0-9-]/, '')}]
|
83
|
+
end.flatten.sort
|
84
|
+
|
85
|
+
other_op_values = other_trx[:operations].map do |type, value|
|
86
|
+
[type.to_s, value.values.map{|v| transform_value(v).gsub(/[^a-zA-Z0-9-]/, '')}]
|
87
|
+
end.flatten.sort
|
88
|
+
|
89
|
+
# unless op_values == other_op_values
|
90
|
+
# require 'pry'; binding.pry
|
91
|
+
# end
|
92
|
+
|
93
|
+
op_values == other_op_values
|
94
|
+
rescue => e
|
95
|
+
# require 'pry'; binding.pry
|
96
|
+
|
97
|
+
false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
private
|
101
|
+
def transform_value(v)
|
102
|
+
case v
|
103
|
+
when Array then v.map{|e| transform_value(e)}.to_s
|
104
|
+
when Hash
|
105
|
+
if !v['tl_in_read'] && !!v['value']
|
106
|
+
v['value'].to_s
|
107
|
+
else
|
108
|
+
v.values.map{|e| transform_value(e)}.to_s
|
109
|
+
end
|
110
|
+
else
|
111
|
+
v.to_s
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,406 @@
|
|
1
|
+
module Hive
|
2
|
+
# {TransactionBuilder} can be used to create a transaction that the
|
3
|
+
# {NetworkBroadcastApi} can broadcast to the rest of the platform. The main
|
4
|
+
# feature of this class is the ability to cryptographically sign the
|
5
|
+
# transaction so that it conforms to the consensus rules that are required by
|
6
|
+
# the blockchain.
|
7
|
+
#
|
8
|
+
# wif = '5JrvPrQeBBvCRdjv29iDvkwn3EQYZ9jqfAHzrCyUvfbEbRkrYFC'
|
9
|
+
# builder = Hive::TransactionBuilder.new(wif: wif)
|
10
|
+
# builder.put(vote: {
|
11
|
+
# voter: 'alice',
|
12
|
+
# author: 'bob',
|
13
|
+
# permlink: 'my-burgers',
|
14
|
+
# weight: 10000
|
15
|
+
# })
|
16
|
+
#
|
17
|
+
# trx = builder.transaction
|
18
|
+
# network_broadcast_api = Hive::CondenserApi.new
|
19
|
+
# network_broadcast_api.broadcast_transaction_synchronous(trx: trx)
|
20
|
+
#
|
21
|
+
#
|
22
|
+
# The `wif` value may also be an array, when signing with multiple signatures
|
23
|
+
# (multisig).
|
24
|
+
class TransactionBuilder
|
25
|
+
include Retriable
|
26
|
+
include ChainConfig
|
27
|
+
include Utils
|
28
|
+
|
29
|
+
attr_accessor :app_base, :database_api, :block_api, :expiration, :operations
|
30
|
+
attr_writer :wif
|
31
|
+
attr_reader :signed, :testnet, :force_serialize
|
32
|
+
|
33
|
+
alias app_base? app_base
|
34
|
+
alias testnet? testnet
|
35
|
+
alias force_serialize? force_serialize
|
36
|
+
|
37
|
+
def initialize(options = {})
|
38
|
+
@app_base = !!options[:app_base] # default false
|
39
|
+
@database_api = options[:database_api]
|
40
|
+
@block_api = options[:block_api]
|
41
|
+
|
42
|
+
if app_base?
|
43
|
+
@database_api ||= Hive::DatabaseApi.new(options)
|
44
|
+
@block_api ||= Hive::BlockApi.new(options)
|
45
|
+
else
|
46
|
+
@database_api ||= Hive::CondenserApi.new(options)
|
47
|
+
@block_api ||= Hive::CondenserApi.new(options)
|
48
|
+
end
|
49
|
+
|
50
|
+
@wif = [options[:wif]].flatten
|
51
|
+
@signed = false
|
52
|
+
@testnet = !!options[:testnet]
|
53
|
+
@force_serialize = !!options[:force_serialize]
|
54
|
+
|
55
|
+
if !!(trx = options[:trx])
|
56
|
+
trx = case trx
|
57
|
+
when String then JSON[trx]
|
58
|
+
else; trx
|
59
|
+
end
|
60
|
+
|
61
|
+
@trx = Transaction.new(trx)
|
62
|
+
end
|
63
|
+
|
64
|
+
@trx ||= Transaction.new
|
65
|
+
@chain = options[:chain] || :hive
|
66
|
+
@error_pipe = options[:error_pipe] || STDERR
|
67
|
+
@chain_id = options[:chain_id] || ENV['HIVE_CHAIN_ID']
|
68
|
+
|
69
|
+
@network_chain_id ||= case @chain
|
70
|
+
when :hive then @database_api.get_config{|config| config['HIVE_CHAIN_ID']} rescue NETWORKS_HIVE_CHAIN_ID
|
71
|
+
when :test then @database_api.get_config{|config| config['HIVE_CHAIN_ID']} rescue NETWORKS_TEST_CHAIN_ID
|
72
|
+
else; raise UnsupportedChainError, "Unsupported chain: #{@chain}"
|
73
|
+
end
|
74
|
+
|
75
|
+
@chain_id ||= @network_chain_id
|
76
|
+
|
77
|
+
if testnet? && (@chain_id == NETWORKS_HIVE_CHAIN_ID || @chain_id == NETWORKS_HIVE_LEGACY_CHAIN_ID)
|
78
|
+
raise UnsupportedChainError, "Unsupported testnet chain id: #{@chain_id}"
|
79
|
+
end
|
80
|
+
|
81
|
+
if @chain_id != @network_chain_id
|
82
|
+
raise UnsupportedChainError, "Unsupported chain id (expected: #{@chain_id}, network was: #{@network_chain_id})"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def inspect
|
87
|
+
properties = %w(trx).map do |prop|
|
88
|
+
if !!(v = instance_variable_get("@#{prop}"))
|
89
|
+
"@#{prop}=#{v.inspect}"
|
90
|
+
end
|
91
|
+
end.compact.join(', ')
|
92
|
+
|
93
|
+
"#<#{self.class.name} [#{properties}]>"
|
94
|
+
end
|
95
|
+
|
96
|
+
def reset
|
97
|
+
@trx = Transaction.new
|
98
|
+
@signed = false
|
99
|
+
|
100
|
+
self
|
101
|
+
end
|
102
|
+
|
103
|
+
# If the transaction can be prepared, this method will do so and set the
|
104
|
+
# expiration. Once the expiration is set, it will not re-prepare. If you
|
105
|
+
# call {#put}, the expiration is set {::Nil} so that it can be re-prepared.
|
106
|
+
#
|
107
|
+
# Usually, this method is called automatically by {#put} and/or {#transaction}.
|
108
|
+
#
|
109
|
+
# @return {TransactionBuilder}
|
110
|
+
def prepare
|
111
|
+
if @trx.expired?
|
112
|
+
catch :prepare_header do; begin
|
113
|
+
@database_api.get_dynamic_global_properties do |properties|
|
114
|
+
block_number = properties.last_irreversible_block_num
|
115
|
+
block_header_args = if app_base?
|
116
|
+
{block_num: block_number}
|
117
|
+
else
|
118
|
+
block_number
|
119
|
+
end
|
120
|
+
|
121
|
+
@block_api.get_block_header(block_header_args) do |result|
|
122
|
+
header = if app_base?
|
123
|
+
result.header
|
124
|
+
else
|
125
|
+
result
|
126
|
+
end
|
127
|
+
|
128
|
+
@trx.ref_block_num = (block_number - 1) & 0xFFFF
|
129
|
+
@trx.ref_block_prefix = unhexlify(header.previous[8..-1]).unpack('V*')[0]
|
130
|
+
@trx.expiration ||= (Time.parse(properties.time + 'Z') + EXPIRE_IN_SECS).utc
|
131
|
+
end
|
132
|
+
end
|
133
|
+
rescue => e
|
134
|
+
if can_retry? e
|
135
|
+
@error_pipe.puts "#{e} ... retrying."
|
136
|
+
throw :prepare_header
|
137
|
+
else
|
138
|
+
raise e
|
139
|
+
end
|
140
|
+
end; end
|
141
|
+
end
|
142
|
+
|
143
|
+
self
|
144
|
+
end
|
145
|
+
|
146
|
+
# Sets operations all at once, then prepares.
|
147
|
+
def operations=(operations)
|
148
|
+
@trx.operations = operations.map{ |op| normalize_operation(op) }
|
149
|
+
prepare
|
150
|
+
@trx.operations
|
151
|
+
end
|
152
|
+
|
153
|
+
# A quick and flexible way to append a new operation to the transaction.
|
154
|
+
# This method uses ducktyping to figure out how to form the operation.
|
155
|
+
#
|
156
|
+
# There are three main ways you can call this method. These assume that
|
157
|
+
# `op_type` is a {::Symbol} (or {::String}) representing the type of operation and `op` is the
|
158
|
+
# operation {::Hash}.
|
159
|
+
#
|
160
|
+
# put(op_type, op)
|
161
|
+
#
|
162
|
+
# ... or ...
|
163
|
+
#
|
164
|
+
# put(op_type => op)
|
165
|
+
#
|
166
|
+
# ... or ...
|
167
|
+
#
|
168
|
+
# put([op_type, op])
|
169
|
+
#
|
170
|
+
# You can also chain multiple operations:
|
171
|
+
#
|
172
|
+
# builder = Hive::TransactionBuilder.new
|
173
|
+
# builder.put(vote: vote1).put(vote: vote2)
|
174
|
+
# @return {TransactionBuilder}
|
175
|
+
def put(type, op = nil)
|
176
|
+
@trx.expiration = nil
|
177
|
+
@trx.operations << normalize_operation(type, op)
|
178
|
+
prepare
|
179
|
+
self
|
180
|
+
end
|
181
|
+
|
182
|
+
# If all of the required values are set, this returns a fully formed
|
183
|
+
# transaction that is ready to broadcast.
|
184
|
+
#
|
185
|
+
# @return
|
186
|
+
# {
|
187
|
+
# :ref_block_num => 18912,
|
188
|
+
# :ref_block_prefix => 575781536,
|
189
|
+
# :expiration => "2018-04-26T15:26:12",
|
190
|
+
# :extensions => [],
|
191
|
+
# :operations => [[:vote, {
|
192
|
+
# :voter => "alice",
|
193
|
+
# :author => "bob",
|
194
|
+
# :permlink => "my-burgers",
|
195
|
+
# :weight => 10000
|
196
|
+
# }
|
197
|
+
# ]],
|
198
|
+
# :signatures => ["1c45b65740b4b2c17c4bcf6bcc3f8d90ddab827d50532729fc3b8f163f2c465a532b0112ae4bf388ccc97b7c2e0bc570caadda78af48cf3c261037e65eefcd941e"]
|
199
|
+
# }
|
200
|
+
def transaction(options = {prepare: true, sign: true})
|
201
|
+
options[:prepare] = true unless options.has_key? :prepare
|
202
|
+
options[:sign] = true unless options.has_key? :sign
|
203
|
+
|
204
|
+
prepare if !!options[:prepare]
|
205
|
+
|
206
|
+
if !!options[:sign]
|
207
|
+
sign
|
208
|
+
else
|
209
|
+
@trx
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Appends to the `signatures` array of the transaction, built from a
|
214
|
+
# serialized digest.
|
215
|
+
#
|
216
|
+
# @return {Hash | TransactionBuilder} The fully signed transaction if a `wif` is provided or the instance of the {TransactionBuilder} if a `wif` has not yet been provided.
|
217
|
+
def sign
|
218
|
+
return self if @wif.empty?
|
219
|
+
return self if @trx.expired?
|
220
|
+
|
221
|
+
unless @signed
|
222
|
+
catch :serialize do; begin
|
223
|
+
transaction_hex.tap do |result|
|
224
|
+
hex = if app_base?
|
225
|
+
result.hex
|
226
|
+
else
|
227
|
+
result
|
228
|
+
end
|
229
|
+
|
230
|
+
unless force_serialize?
|
231
|
+
derrived_trx = Transaction.new(hex: hex)
|
232
|
+
derrived_ops = derrived_trx.operations
|
233
|
+
derrived_trx.operations = derrived_ops.map do |op|
|
234
|
+
op_name = if app_base?
|
235
|
+
op[:type].to_sym
|
236
|
+
else
|
237
|
+
op[:type].to_s.sub(/_operation$/, '').to_sym
|
238
|
+
end
|
239
|
+
|
240
|
+
normalize_operation op_name, JSON[op[:value].to_json]
|
241
|
+
end
|
242
|
+
|
243
|
+
unless @trx == derrived_trx
|
244
|
+
if defined? JsonCompare
|
245
|
+
raise SerializationMismatchError, JSON.pretty_generate({trx: @trx, derrived_trx: derrived_trx})
|
246
|
+
else
|
247
|
+
raise SerializationMismatchError
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
hex = hex[0..-4] # drop empty signature array
|
253
|
+
@trx.id = Digest::SHA256.hexdigest(unhexlify(hex))[0..39]
|
254
|
+
|
255
|
+
hex = @chain_id + hex
|
256
|
+
digest = unhexlify(hex)
|
257
|
+
digest_hex = Digest::SHA256.digest(digest)
|
258
|
+
private_keys = @wif.map{ |wif| Bitcoin::Key.from_base58 wif }
|
259
|
+
ec = Bitcoin::OpenSSL_EC
|
260
|
+
count = 0
|
261
|
+
|
262
|
+
private_keys.each do |private_key|
|
263
|
+
sig = nil
|
264
|
+
|
265
|
+
loop do
|
266
|
+
count += 1
|
267
|
+
@error_pipe.puts "#{count} attempts to find canonical signature" if count % 40 == 0
|
268
|
+
public_key_hex = private_key.pub
|
269
|
+
sig = ec.sign_compact(digest_hex, private_key.priv, public_key_hex, false)
|
270
|
+
|
271
|
+
next if public_key_hex != ec.recover_compact(digest_hex, sig)
|
272
|
+
break if canonical? sig
|
273
|
+
end
|
274
|
+
|
275
|
+
@trx.signatures << hexlify(sig)
|
276
|
+
end
|
277
|
+
|
278
|
+
@signed = true
|
279
|
+
end
|
280
|
+
rescue => e
|
281
|
+
if can_retry? e
|
282
|
+
@error_pipe.puts "#{e} ... retrying."
|
283
|
+
throw :serialize
|
284
|
+
else
|
285
|
+
raise e
|
286
|
+
end
|
287
|
+
end; end
|
288
|
+
end
|
289
|
+
|
290
|
+
@trx
|
291
|
+
end
|
292
|
+
|
293
|
+
def transaction_hex
|
294
|
+
trx = transaction(prepare: true, sign: false)
|
295
|
+
|
296
|
+
transaction_hex_args = if app_base?
|
297
|
+
{trx: trx}
|
298
|
+
else
|
299
|
+
trx
|
300
|
+
end
|
301
|
+
|
302
|
+
@database_api.get_transaction_hex(transaction_hex_args) do |result|
|
303
|
+
if app_base?
|
304
|
+
result[:hex]
|
305
|
+
else
|
306
|
+
result
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
# @return [Array] All public keys that could possibly sign for a given transaction.
|
312
|
+
def potential_signatures
|
313
|
+
potential_signatures_args = if app_base?
|
314
|
+
{trx: transaction}
|
315
|
+
else
|
316
|
+
transaction
|
317
|
+
end
|
318
|
+
|
319
|
+
@database_api.get_potential_signatures(potential_signatures_args) do |result|
|
320
|
+
if app_base?
|
321
|
+
result[:keys]
|
322
|
+
else
|
323
|
+
result
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
# This API will take a partially signed transaction and a set of public keys
|
329
|
+
# that the owner has the ability to sign for and return the minimal subset
|
330
|
+
# of public keys that should add signatures to the transaction.
|
331
|
+
#
|
332
|
+
# @return [Array] The minimal subset of public keys that should add signatures to the transaction.
|
333
|
+
def required_signatures
|
334
|
+
required_signatures_args = if app_base?
|
335
|
+
{trx: transaction}
|
336
|
+
else
|
337
|
+
[transaction, []]
|
338
|
+
end
|
339
|
+
|
340
|
+
@database_api.get_required_signatures(*required_signatures_args) do |result|
|
341
|
+
if app_base?
|
342
|
+
result[:keys]
|
343
|
+
else
|
344
|
+
result
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
# @return [Boolean] True if the transaction has all of the required signatures.
|
350
|
+
def valid?
|
351
|
+
verify_authority_args = if app_base?
|
352
|
+
{trx: transaction}
|
353
|
+
else
|
354
|
+
transaction
|
355
|
+
end
|
356
|
+
|
357
|
+
@database_api.verify_authority(verify_authority_args) do |result|
|
358
|
+
if app_base?
|
359
|
+
result.valid
|
360
|
+
else
|
361
|
+
result
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
private
|
366
|
+
# See: https://github.com/openhive-network/hive/pull/2500
|
367
|
+
# @private
|
368
|
+
def canonical?(sig)
|
369
|
+
sig = sig.unpack('C*')
|
370
|
+
|
371
|
+
!(
|
372
|
+
((sig[0] & 0x80 ) != 0) || ( sig[0] == 0 ) ||
|
373
|
+
((sig[1] & 0x80 ) != 0) ||
|
374
|
+
((sig[32] & 0x80 ) != 0) || ( sig[32] == 0 ) ||
|
375
|
+
((sig[33] & 0x80 ) != 0)
|
376
|
+
)
|
377
|
+
end
|
378
|
+
|
379
|
+
def normalize_operation(type, op = nil)
|
380
|
+
if app_base?
|
381
|
+
case type
|
382
|
+
when Symbol, String
|
383
|
+
type_value = "#{type}_operation"
|
384
|
+
{type: type_value, value: op}
|
385
|
+
when Hash
|
386
|
+
type_value = "#{type.keys.first}_operation"
|
387
|
+
{type: type_value, value: type.values.first}
|
388
|
+
when Array
|
389
|
+
type_value = "#{type[0]}_operation"
|
390
|
+
{type: type_value, value: type[1]}
|
391
|
+
else
|
392
|
+
raise Hive::ArgumentError, "Don't know what to do with operation type #{type.class}: #{type} (#{op})"
|
393
|
+
end
|
394
|
+
else
|
395
|
+
case type
|
396
|
+
when Symbol then [type, op]
|
397
|
+
when String then [type.to_sym, op]
|
398
|
+
when Hash then [type.keys.first.to_sym, type.values.first]
|
399
|
+
when Array then type
|
400
|
+
else
|
401
|
+
raise Hive::ArgumentError, "Don't know what to do with operation type #{type.class}: #{type} (#{op})"
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|