steem-ruby 0.1.1 → 0.9.0

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 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