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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +48 -0
- data/Rakefile +3 -1
- data/lib/steem/api.rb +14 -3
- data/lib/steem/rpc/base_client.rb +2 -2
- data/lib/steem/rpc/http_client.rb +4 -3
- data/lib/steem/transaction_builder.rb +79 -40
- data/lib/steem/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c0d2e4cfae2885bc24acaa7d2a5f046e73a1505
|
4
|
+
data.tar.gz: ddfdd7c5505ada6ea5e67584e51f6b5241e74bb6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec9da3a5222a1ef5bf503ab496eeb12b88609ff1b76d0fd888ca10a355fa3bf0c1bea497a75327a5922a7b002d7b8f19d38eb9b047b431d5fa532eea740c3fa3
|
7
|
+
data.tar.gz: 3a3aac4b912eeb02c92c97b9e74be3c747595b0acfffd75318258d4b89b99b1569776ff9aa4d7393aa0176b45b2170f5723195aafc87729ae7e22f9cb733d944
|
data/Gemfile.lock
CHANGED
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
|
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
|
-
#
|
43
|
-
|
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
|
-
|
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(
|
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,
|
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,
|
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, :
|
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
|
-
@
|
37
|
-
@
|
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
|
-
|
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
|
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
|
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
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
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
|
-
|
228
|
-
|
268
|
+
@signed = true
|
269
|
+
trx[:signatures] = @signatures
|
229
270
|
end
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
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/
|
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
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.
|
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-
|
11
|
+
date: 2018-05-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|