steem-ruby 0.1.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 24650742975337b2832ba5ab426f609a5ff8c9ef
4
- data.tar.gz: 827c4848766e9b54dc8f04f5e20c920d3964c4eb
3
+ metadata.gz: 4c0d2e4cfae2885bc24acaa7d2a5f046e73a1505
4
+ data.tar.gz: ddfdd7c5505ada6ea5e67584e51f6b5241e74bb6
5
5
  SHA512:
6
- metadata.gz: 40b66f82639c1db97bf844bc07414f4ef512decec1fa4bf99bbc789ac1916379d96d03eebc417a89a7fcc7dcbbcbd9c29d82c3dd08958e3331712b2af4450f04
7
- data.tar.gz: 37183706e9bdb150df65afb03d697c6a8cca050d3e3a3ac605054e46ba10d6a0897dc28e55a8035cc10f0a8df7d144fe548bd887344b132fa47f29518ae945be
6
+ metadata.gz: ec9da3a5222a1ef5bf503ab496eeb12b88609ff1b76d0fd888ca10a355fa3bf0c1bea497a75327a5922a7b002d7b8f19d38eb9b047b431d5fa532eea740c3fa3
7
+ data.tar.gz: 3a3aac4b912eeb02c92c97b9e74be3c747595b0acfffd75318258d4b89b99b1569776ff9aa4d7393aa0176b45b2170f5723195aafc87729ae7e22f9cb733d944
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- steem-ruby (0.1.1)
4
+ steem-ruby (0.9.0)
5
5
  bitcoin-ruby (~> 0.0, >= 0.0.18)
6
6
  ffi (~> 1.9, >= 1.9.23)
7
7
  hashie (~> 3.5, >= 3.5.7)
data/README.md CHANGED
@@ -7,6 +7,8 @@ Steem-ruby the Ruby API for Steem blockchain.
7
7
 
8
8
  Full documentation: http://www.rubydoc.info/gems/steem-ruby
9
9
 
10
+ **Note:** *This library depends on AppBase methods that are a work in progress.*
11
+
10
12
  ## `radiator` vs. `steem-ruby`
11
13
 
12
14
  The `steem-ruby` gem was written from the ground up by `@inertia`, who is also the author of [`radiator`](https://github.com/inertia186/radiator).
@@ -68,6 +70,52 @@ end
68
70
 
69
71
  *See: [Broadcast](https://www.rubydoc.info/gems/steem-ruby/Steem/Broadcast)*
70
72
 
73
+ ### Multisig
74
+
75
+ You can use multisignature to broadcast an operation.
76
+
77
+ ```ruby
78
+ params = {
79
+ voter: voter,
80
+ author: author,
81
+ permlink: permlink,
82
+ weight: weight
83
+ }
84
+
85
+ Steem::Broadcast.vote(wif: [wif1, wif2], params: params) do |result|
86
+ puts result
87
+ end
88
+ ```
89
+
90
+ In addition to signing with multiple `wif` private keys, it is possible to also export a partially signed transaction to have signing completed by someone else.
91
+
92
+ ```ruby
93
+ builder = TransactionBuilder.new(wif: wif1)
94
+
95
+ builder.put(vote: {
96
+ voter: voter,
97
+ author: author,
98
+ permlink: permlink,
99
+ weight: weight
100
+ })
101
+
102
+ trx = builder.sign.to_json
103
+
104
+ File.open('trx.json', 'w') do |f|
105
+ f.write(trx)
106
+ end
107
+ ```
108
+
109
+ Then send the contents of `trx.json` to the other signing party so they can privately sign and broadcast the transaction.
110
+
111
+ ```ruby
112
+ trx = open('trx.json').read
113
+ builder = TransactionBuilder.new(wif: wif2, trx: trx)
114
+ api = Steem::NetworkBroadcastApi.new
115
+ trx = builder.transaction
116
+ api.broadcast_transaction_synchronous(trx: trx)
117
+ ```
118
+
71
119
  ### Get Accounts
72
120
 
73
121
  ```ruby
data/Rakefile CHANGED
@@ -79,6 +79,8 @@ namespace :test do
79
79
 
80
80
  desc 'Tests the API using multiple threads.'
81
81
  task :threads do
82
+ next if !!ENV['TEST']
83
+
82
84
  threads = []
83
85
  api = Steem::Api.new(url: ENV['TEST_NODE'])
84
86
  database_api = Steem::DatabaseApi.new(url: ENV['TEST_NODE'])
@@ -146,7 +148,7 @@ namespace :stream do
146
148
  loop do
147
149
  api.get_dynamic_global_properties do |properties|
148
150
  current_block_num = properties.last_irreversible_block_num
149
- # First pass replays latest 100 blocks.
151
+ # First pass replays latest a random number of blocks to test chunking.
150
152
  first_block_num ||= current_block_num - (rand * 200).to_i
151
153
 
152
154
  if current_block_num >= first_block_num
data/lib/steem/api.rb CHANGED
@@ -39,8 +39,8 @@ module Steem
39
39
  attr_accessor :chain, :methods
40
40
 
41
41
  # Use this for debugging naive thread handler.
42
- # DEFAULT_RPC_CLIENT = RPC::BaseClient
43
- DEFAULT_RPC_CLIENT = RPC::ThreadSafeHttpClient
42
+ # DEFAULT_RPC_CLIENT_CLASS = RPC::BaseClient
43
+ DEFAULT_RPC_CLIENT_CLASS = RPC::ThreadSafeHttpClient
44
44
 
45
45
  def self.api_name=(api_name)
46
46
  @api_name = api_name.to_s.
@@ -65,11 +65,22 @@ module Steem
65
65
  @jsonrpc
66
66
  end
67
67
 
68
+ # Override this if you want to use your own client.
69
+ def self.default_rpc_client_class
70
+ DEFAULT_RPC_CLIENT_CLASS
71
+ end
72
+
68
73
  def initialize(options = {})
69
74
  @chain = options[:chain] || :steem
70
75
  @error_pipe = options[:error_pipe] || STDERR
71
76
  @api_name = self.class.api_name ||= :condenser_api
72
- @rpc_client = options[:rpc_client] || DEFAULT_RPC_CLIENT.new(options.merge(api_name: @api_name))
77
+
78
+ @rpc_client = if !!options[:rpc_client]
79
+ options[:rpc_client]
80
+ else
81
+ rpc_client_class = self.class.default_rpc_client_class
82
+ rpc_client_class.new(options.merge(api_name: @api_name))
83
+ end
73
84
 
74
85
  if @api_name == :jsonrpc
75
86
  Api::jsonrpc = self
@@ -3,7 +3,7 @@ module Steem
3
3
  class BaseClient
4
4
  include ChainConfig
5
5
 
6
- attr_accessor :chain, :error_pipe
6
+ attr_accessor :url, :chain, :error_pipe
7
7
 
8
8
  # @private
9
9
  MAX_TIMEOUT_RETRY_COUNT = 100
@@ -27,7 +27,7 @@ module Steem
27
27
  end
28
28
 
29
29
  def uri
30
- @uri ||= URI.parse(@url)
30
+ @uri ||= URI.parse(url)
31
31
  end
32
32
 
33
33
  # Adds a request object to the stack. Usually, this method is called
@@ -16,7 +16,8 @@ module Steem
16
16
  # http status code of 200 and HTML page.
17
17
  #
18
18
  # @private
19
- TIMEOUT_ERRORS = [Net::OpenTimeout, Net::ReadTimeout, JSON::ParserError]
19
+ TIMEOUT_ERRORS = [Net::OpenTimeout, JSON::ParserError, Net::ReadTimeout,
20
+ Errno::EBADF, Errno::ECONNREFUSED, IOError]
20
21
 
21
22
  # @private
22
23
  POST_HEADERS = {
@@ -28,7 +29,7 @@ module Steem
28
29
 
29
30
  def http
30
31
  @http ||= Net::HTTP.new(uri.host, uri.port).tap do |http|
31
- http.use_ssl = true
32
+ http.use_ssl = true if uri.to_s =~ /^https/i
32
33
  http.keep_alive_timeout = 2 # seconds
33
34
 
34
35
  # WARNING This method opens a serious security hole. Never use this
@@ -66,7 +67,7 @@ module Steem
66
67
  end
67
68
 
68
69
  if request_object.size > JSON_RPC_BATCH_SIZE_MAXIMUM
69
- raise JsonRpcBatchMaximumSizeExceededError, 'Maximum json-rpc-batch is 50 elements.'
70
+ raise JsonRpcBatchMaximumSizeExceededError, "Maximum json-rpc-batch is #{JSON_RPC_BATCH_SIZE_MAXIMUM} elements."
70
71
  end
71
72
 
72
73
  request.body = if request_object.class == Hash
@@ -18,23 +18,52 @@ module Steem
18
18
  # network_broadcast_api = Steem::NetworkBroadcastApi.new
19
19
  # network_broadcast_api.broadcast_transaction_synchronous(trx: trx)
20
20
  #
21
+ #
22
+ # The `wif` value may also be an array, when signing with multiple signatures
23
+ # (multisig).
21
24
  class TransactionBuilder
22
25
  include Retriable
23
26
  include ChainConfig
24
27
  include Utils
25
28
 
26
- attr_accessor :database_api, :block_api, :wif, :expiration, :operations
29
+ attr_accessor :database_api, :block_api, :expiration, :operations
30
+ attr_writer :wif
31
+ attr_reader :signed
27
32
 
28
33
  def initialize(options = {})
29
34
  @database_api = options[:database_api] || Steem::DatabaseApi.new(options)
30
35
  @block_api = options[:block_api] || Steem::BlockApi.new(options)
31
- @wif = options[:wif]
36
+ @wif = [options[:wif]].flatten
37
+ @signed = false
38
+
39
+ if !!(trx = options[:trx])
40
+ trx = case trx
41
+ when String then JSON[trx]
42
+ else; trx
43
+ end
44
+
45
+ trx_options = {
46
+ ref_block_num: trx['ref_block_num'],
47
+ ref_block_prefix: trx['ref_block_prefix'],
48
+ extensions: (trx['extensions']),
49
+ operations: trx['operations'],
50
+ signatures: (trx['signatures']),
51
+ }
52
+
53
+ trx_options[:expiration] = case trx['expiration']
54
+ when String then Time.parse(trx['expiration'] + 'Z')
55
+ else; trx['expiration']
56
+ end
57
+
58
+ options = options.merge(trx_options)
59
+ end
60
+
32
61
  @ref_block_num = options[:ref_block_num]
33
62
  @ref_block_prefix = options[:ref_block_prefix]
34
- @expiration = nil
35
63
  @operations = options[:operations] || []
36
- @extensions = []
37
- @signatures = []
64
+ @expiration = options[:expiration]
65
+ @extensions = options[:extensions] || []
66
+ @signatures = options[:signatures] || []
38
67
  @chain = options[:chain] || :steem
39
68
  @error_pipe = options[:error_pipe] || STDERR
40
69
  @chain_id = case @chain
@@ -46,8 +75,8 @@ module Steem
46
75
 
47
76
  def inspect
48
77
  properties = %w(
49
- ref_block_num ref_block_prefix expiration operations
50
- extensions signatures
78
+ ref_block_num ref_block_prefix expiration operations extensions
79
+ signatures
51
80
  ).map do |prop|
52
81
  if !!(v = instance_variable_get("@#{prop}"))
53
82
  "@#{prop}=#{v}"
@@ -64,6 +93,7 @@ module Steem
64
93
  @operations = []
65
94
  @extensions = []
66
95
  @signatures = []
96
+ @signed = false
67
97
 
68
98
  self
69
99
  end
@@ -90,7 +120,7 @@ module Steem
90
120
 
91
121
  @ref_block_num = (block_number - 1) & 0xFFFF
92
122
  @ref_block_prefix = unhexlify(header.previous[8..-1]).unpack('V*')[0]
93
- @expiration = (Time.parse(properties.time + 'Z') + EXPIRE_IN_SECS).utc
123
+ @expiration ||= (Time.parse(properties.time + 'Z') + EXPIRE_IN_SECS).utc
94
124
  end
95
125
  end
96
126
  rescue => e
@@ -196,7 +226,7 @@ module Steem
196
226
  #
197
227
  # @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.
198
228
  def sign
199
- return self unless !!@wif
229
+ return self if @wif.empty?
200
230
  return self if expired?
201
231
 
202
232
  trx = {
@@ -208,38 +238,47 @@ module Steem
208
238
  signatures: @signatures
209
239
  }
210
240
 
211
- catch :serialize do; begin
212
- @database_api.get_transaction_hex(trx: trx) do |result|
213
- hex = @chain_id + result.hex[0..-4] # Why do we have to chop the last two bytes?
214
- digest = unhexlify(hex)
215
- digest_hex = Digest::SHA256.digest(digest)
216
- private_key = Bitcoin::Key.from_base58 @wif
217
- public_key_hex = private_key.pub
218
- ec = Bitcoin::OpenSSL_EC
219
- count = 0
220
- sig = nil
221
-
222
- loop do
223
- count += 1
224
- @error_pipe.puts "#{count} attempts to find canonical signature" if count % 40 == 0
225
- sig = ec.sign_compact(digest_hex, private_key.priv, public_key_hex, false)
241
+ unless @signed
242
+ catch :serialize do; begin
243
+ @database_api.get_transaction_hex(trx: trx) do |result|
244
+ hex = @chain_id + result.hex[0..-4] # Why do we have to chop the last two bytes?
245
+ digest = unhexlify(hex)
246
+ digest_hex = Digest::SHA256.digest(digest)
247
+ private_keys = @wif.map{ |wif| Bitcoin::Key.from_base58 wif }
248
+ ec = Bitcoin::OpenSSL_EC
249
+ count = 0
250
+ sigs = []
251
+
252
+ private_keys.each do |private_key|
253
+ sig = nil
254
+
255
+ loop do
256
+ count += 1
257
+ @error_pipe.puts "#{count} attempts to find canonical signature" if count % 40 == 0
258
+ public_key_hex = private_key.pub
259
+ sig = ec.sign_compact(digest_hex, private_key.priv, public_key_hex, false)
260
+
261
+ next if public_key_hex != ec.recover_compact(digest_hex, sig)
262
+ break if canonical? sig
263
+ end
264
+
265
+ @signatures << hexlify(sig)
266
+ end
226
267
 
227
- next if public_key_hex != ec.recover_compact(digest_hex, sig)
228
- break if canonical? sig
268
+ @signed = true
269
+ trx[:signatures] = @signatures
229
270
  end
230
-
231
- trx[:signatures] = @signatures = [hexlify(sig)]
232
- end
233
- rescue => e
234
- if can_retry? e
235
- @error_pipe.puts "#{e} ... retrying."
236
- throw :serialize
237
- else
238
- raise e
239
- end
240
- end; end
241
-
242
- trx
271
+ rescue => e
272
+ if can_retry? e
273
+ @error_pipe.puts "#{e} ... retrying."
274
+ throw :serialize
275
+ else
276
+ raise e
277
+ end
278
+ end; end
279
+ end
280
+
281
+ Hashie::Mash.new trx
243
282
  end
244
283
 
245
284
  # @return [Array] All public keys that could possibly sign for a given transaction.
@@ -267,7 +306,7 @@ module Steem
267
306
  end
268
307
  end
269
308
  private
270
- # See: https://github.com/steemit/steem/issues/1944
309
+ # See: https://github.com/steemit/steem/pull/2500
271
310
  # @private
272
311
  def canonical?(sig)
273
312
  sig = sig.unpack('C*')
data/lib/steem/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Steem
2
- VERSION = '0.1.1'
2
+ VERSION = '0.9.0'
3
3
  AGENT_ID = "steem-ruby/#{VERSION}"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: steem-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anthony Martin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-22 00:00:00.000000000 Z
11
+ date: 2018-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler