nanook 2.4.0 → 3.1.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/CHANGELOG.md +139 -0
- data/README.md +158 -99
- data/bin/console +4 -3
- data/lib/nanook.rb +77 -21
- data/lib/nanook/account.rb +238 -168
- data/lib/nanook/block.rb +380 -149
- data/lib/nanook/errors.rb +10 -0
- data/lib/nanook/node.rb +218 -83
- data/lib/nanook/private_key.rb +115 -0
- data/lib/nanook/public_key.rb +55 -0
- data/lib/nanook/rpc.rb +104 -44
- data/lib/nanook/util.rb +68 -19
- data/lib/nanook/version.rb +3 -1
- data/lib/nanook/wallet.rb +369 -167
- data/lib/nanook/wallet_account.rb +151 -91
- data/lib/nanook/work_peer.rb +14 -7
- metadata +28 -27
- data/lib/nanook/error.rb +0 -5
- data/lib/nanook/key.rb +0 -46
data/lib/nanook/node.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'util'
|
2
4
|
|
5
|
+
class Nanook
|
3
6
|
# The <tt>Nanook::Node</tt> class contains methods to manage your nano
|
4
7
|
# node and query its data of the nano network.
|
5
8
|
#
|
@@ -25,6 +28,7 @@ class Nanook
|
|
25
28
|
# rpc_conn = Nanook::Rpc.new
|
26
29
|
# node = Nanook::Node.new(rpc_conn)
|
27
30
|
class Node
|
31
|
+
include Nanook::Util
|
28
32
|
|
29
33
|
def initialize(rpc)
|
30
34
|
@rpc = rpc
|
@@ -38,93 +42,162 @@ class Nanook
|
|
38
42
|
#
|
39
43
|
# @return [Integer] number of accounts with _open_ blocks.
|
40
44
|
def account_count
|
41
|
-
rpc(:frontier_count
|
45
|
+
rpc(:frontier_count, _access: :count)
|
42
46
|
end
|
43
|
-
|
47
|
+
alias frontier_count account_count
|
44
48
|
|
45
49
|
# The count of all blocks downloaded to the node, and
|
46
50
|
# blocks still to be synchronized by the node.
|
47
51
|
#
|
48
52
|
# ==== Example:
|
49
53
|
#
|
50
|
-
#
|
54
|
+
# {
|
55
|
+
# count: 100,
|
56
|
+
# unchecked: 10,
|
57
|
+
# cemented: 25
|
58
|
+
# }
|
51
59
|
#
|
52
60
|
# @return [Hash{Symbol=>Integer}] number of blocks and unchecked
|
53
61
|
# synchronizing blocks
|
54
62
|
def block_count
|
55
|
-
rpc(:block_count)
|
63
|
+
rpc(:block_count, _coerce: Hash)
|
56
64
|
end
|
57
65
|
|
58
|
-
#
|
59
|
-
#
|
60
|
-
# ==== Example:
|
66
|
+
# Tells the node to send a keepalive packet to a specific IP address and port.
|
61
67
|
#
|
62
|
-
#
|
63
|
-
|
64
|
-
|
65
|
-
#
|
66
|
-
# {
|
67
|
-
# send: 1000,
|
68
|
-
# receive: 900,
|
69
|
-
# open: 900,
|
70
|
-
# change: 50
|
71
|
-
# }
|
72
|
-
#
|
73
|
-
# @return [Hash{Symbol=>Integer}] number of blocks by type
|
74
|
-
def block_count_by_type
|
75
|
-
rpc(:block_count_type)
|
68
|
+
# @return [Boolean] indicating if the action was successful
|
69
|
+
def keepalive(address:, port:)
|
70
|
+
rpc(:keepalive, address: address, port: port).key?(:started)
|
76
71
|
end
|
77
|
-
alias_method :block_count_type, :block_count_by_type
|
78
72
|
|
79
73
|
# Initialize bootstrap to a specific IP address and port.
|
80
74
|
#
|
81
75
|
# @return [Boolean] indicating if the action was successful
|
82
76
|
def bootstrap(address:, port:)
|
83
|
-
rpc(:bootstrap, address: address, port: port).
|
77
|
+
rpc(:bootstrap, address: address, port: port).key?(:success)
|
84
78
|
end
|
85
79
|
|
86
80
|
# Initialize multi-connection bootstrap to random peers
|
87
81
|
#
|
88
82
|
# @return [Boolean] indicating if the action was successful
|
89
83
|
def bootstrap_any
|
90
|
-
rpc(:bootstrap_any).
|
84
|
+
rpc(:bootstrap_any).key?(:success)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Initialize lazy bootstrap with given block hash
|
88
|
+
#
|
89
|
+
# @param hash [String]
|
90
|
+
# @param force [Boolean] False by default. Manually force closing
|
91
|
+
# of all current bootstraps
|
92
|
+
# @return [Boolean] indicating if the action was successful
|
93
|
+
def bootstrap_lazy(hash, force: false)
|
94
|
+
rpc(:bootstrap_lazy, hash: hash, force: force, _access: :started) == 1
|
91
95
|
end
|
92
96
|
|
93
|
-
# Returns
|
97
|
+
# Returns information about node elections settings and observed network state:
|
98
|
+
#
|
99
|
+
# - `quorum_delta`: delta tally required to rollback block
|
100
|
+
# - `online_weight_quorum_percent`: percentage of online weight for delta
|
101
|
+
# - `online_weight_minimum`: minimum online weight to confirm block
|
102
|
+
# - `online_stake_total`: currently observed online total weight
|
103
|
+
# - `peers_stake_total`: known peers total weight
|
104
|
+
# - `peers_stake_required`: effective stake needed from directly connected peers for quorum
|
94
105
|
#
|
95
106
|
# ==== Example:
|
96
107
|
#
|
97
|
-
# node.
|
108
|
+
# node.confirmation_quorum
|
98
109
|
#
|
99
110
|
# Example response:
|
100
111
|
#
|
101
|
-
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
#
|
107
|
-
#
|
108
|
-
#
|
109
|
-
# }
|
110
|
-
# ]
|
112
|
+
# {
|
113
|
+
# "quorum_delta": 43216377.43025059,
|
114
|
+
# "online_weight_quorum_percent": 50,
|
115
|
+
# "online_weight_minimum": 60000000.0",
|
116
|
+
# "online_stake_total": 86432754.86050119,
|
117
|
+
# "peers_stake_total": 84672338.52479072,
|
118
|
+
# "peers_stake_required": 60000000.0"
|
119
|
+
# }
|
111
120
|
#
|
112
121
|
# @return [Hash{Symbol=>String|Integer}]
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
122
|
+
# @raise [Nanook::NanoUnitError] if `unit` is invalid
|
123
|
+
def confirmation_quorum(unit: Nanook.default_unit)
|
124
|
+
validate_unit!(unit)
|
125
|
+
|
126
|
+
response = rpc(:confirmation_quorum, _coerce: Hash)
|
127
|
+
|
128
|
+
return response unless unit == :nano
|
129
|
+
|
130
|
+
response[:quorum_delta] = raw_to_NANO(response[:quorum_delta])
|
131
|
+
response[:online_weight_minimum] = raw_to_NANO(response[:online_weight_minimum])
|
132
|
+
response[:online_stake_total] = raw_to_NANO(response[:online_stake_total])
|
133
|
+
response[:peers_stake_total] = raw_to_NANO(response[:peers_stake_total])
|
134
|
+
response[:peers_stake_required] = raw_to_NANO(response[:peers_stake_required])
|
135
|
+
|
136
|
+
response.compact
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns the difficulty values (16 hexadecimal digits string, 64 bit)
|
140
|
+
# for the minimum required on the network (network_minimum) as well
|
141
|
+
# as the current active difficulty seen on the network (network_current,
|
142
|
+
# 5 minute trended average of adjusted difficulty seen on confirmed
|
143
|
+
# transactions) which can be used to perform rework for better
|
144
|
+
# prioritization of transaction processing. A multiplier of the
|
145
|
+
# network_current from the base difficulty of network_minimum is also
|
146
|
+
# provided for comparison.
|
147
|
+
#
|
148
|
+
# ==== Example:
|
149
|
+
#
|
150
|
+
# node.difficulty(include_trend: true)
|
151
|
+
#
|
152
|
+
# Example response:
|
153
|
+
#
|
154
|
+
# {
|
155
|
+
# network_minimum: "ffffffc000000000",
|
156
|
+
# network_current: "ffffffc1816766f2",
|
157
|
+
# multiplier: 1.024089858417128,
|
158
|
+
# difficulty_trend: [
|
159
|
+
# 1.156096135149775,
|
160
|
+
# 1.190133894573061,
|
161
|
+
# 1.135567138563921,
|
162
|
+
# 1.000000000000000,
|
163
|
+
# ]
|
164
|
+
# }
|
165
|
+
#
|
166
|
+
# @param include_trend [Boolean] false by default. Also returns the
|
167
|
+
# trend of difficulty seen on the network as a list of multipliers.
|
168
|
+
# Sampling occurs every 16 to 36 seconds. The list is ordered such
|
169
|
+
# that the first value is the most recent sample.
|
170
|
+
# @return [Hash{Symbol=>String|Float|Array}]
|
171
|
+
def difficulty(include_trend: false)
|
172
|
+
rpc(:active_difficulty, include_trend: include_trend, _coerce: Hash).tap do |response|
|
173
|
+
response[:multiplier] = response[:multiplier].to_f
|
174
|
+
|
175
|
+
response[:difficulty_trend].map!(&:to_f) if response.key?(:difficulty_trend)
|
118
176
|
end
|
119
177
|
end
|
120
178
|
|
121
179
|
# @return [String]
|
122
|
-
def
|
123
|
-
|
180
|
+
def to_s
|
181
|
+
self.class.name
|
124
182
|
end
|
183
|
+
alias inspect to_s
|
125
184
|
|
185
|
+
# Returns peers information.
|
186
|
+
#
|
187
|
+
# Example response:
|
188
|
+
#
|
189
|
+
# {
|
190
|
+
# :"[::ffff:104.131.102.132]:7075" => {
|
191
|
+
# protocol_version: 20,
|
192
|
+
# node_id: "node_1y7j5rdqhg99uyab1145gu3yur1ax35a3b6qr417yt8cd6n86uiw3d4whty3",
|
193
|
+
# type: "udp"
|
194
|
+
# },
|
195
|
+
# :"[::ffff:104.131.114.102]:7075" => { ... }
|
196
|
+
# }
|
197
|
+
#
|
198
|
+
# @return [Hash{Symbol=>Hash{Symbol=>Integer|String}}]
|
126
199
|
def peers
|
127
|
-
rpc(:peers
|
200
|
+
rpc(:peers, peer_details: true, _access: :peers, _coerce: Hash)
|
128
201
|
end
|
129
202
|
|
130
203
|
# All representatives and their voting weight.
|
@@ -136,59 +209,86 @@ class Nanook
|
|
136
209
|
# Example response:
|
137
210
|
#
|
138
211
|
# {
|
139
|
-
#
|
140
|
-
#
|
141
|
-
#
|
212
|
+
# Nanook::Account: 3822372327060170000000000000000000000,
|
213
|
+
# Nanook::Account: 30999999999999999999999999000000,
|
214
|
+
# Nanook::Account: 0
|
142
215
|
# }
|
143
216
|
#
|
144
|
-
# @return [Hash{
|
217
|
+
# @return [Hash{Nanook::Account=>Float|Integer}] known representatives and their voting weight
|
218
|
+
# @raise [Nanook::NanoUnitError] if `unit` is invalid
|
145
219
|
def representatives(unit: Nanook.default_unit)
|
146
|
-
|
147
|
-
raise ArgumentError.new("Unsupported unit: #{unit}")
|
148
|
-
end
|
220
|
+
validate_unit!(unit)
|
149
221
|
|
150
|
-
response = rpc(:representatives
|
151
|
-
return response if unit == :raw
|
222
|
+
response = rpc(:representatives, _access: :representatives, _coerce: Hash)
|
152
223
|
|
153
|
-
r = response.map do |account_id,
|
154
|
-
|
224
|
+
r = response.map do |account_id, weight|
|
225
|
+
weight = raw_to_NANO(weight) if unit == :nano
|
155
226
|
|
156
|
-
[account_id,
|
227
|
+
[as_account(account_id), weight]
|
157
228
|
end
|
158
229
|
|
159
|
-
Hash[r]
|
230
|
+
Hash[r]
|
160
231
|
end
|
161
232
|
|
162
|
-
# All online representatives that have voted recently
|
163
|
-
# design of the nano RPC, this method cannot return the voting weight
|
164
|
-
# of the representatives.
|
233
|
+
# All online representatives that have voted recently and their weight.
|
165
234
|
#
|
166
235
|
# ==== Example:
|
167
236
|
#
|
168
|
-
# node.representatives_online # => [
|
237
|
+
# node.representatives_online # => [Nanook::Account, ...]
|
169
238
|
#
|
170
|
-
# @return [
|
239
|
+
# @return [Nanook::Account] array of representative accounts
|
171
240
|
def representatives_online
|
172
|
-
rpc(:representatives_online
|
241
|
+
rpc(:representatives_online, _access: :representatives, _coerce: Array).map do |representative|
|
242
|
+
as_account(representative)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Tells the node to look for any account in all available wallets.
|
247
|
+
#
|
248
|
+
# ==== Example:
|
249
|
+
#
|
250
|
+
# node.search_pending #=> true
|
251
|
+
# @return [Boolean] indicates if the action was successful
|
252
|
+
def search_pending
|
253
|
+
rpc(:search_pending_all).key?(:success)
|
173
254
|
end
|
174
255
|
|
175
256
|
# Safely shuts down the node.
|
176
257
|
#
|
177
258
|
# @return [Boolean] indicating if action was successful
|
178
259
|
def stop
|
179
|
-
rpc(:stop).
|
260
|
+
rpc(:stop).key?(:success)
|
180
261
|
end
|
181
262
|
|
182
263
|
# @param limit [Integer] number of synchronizing blocks to return
|
264
|
+
# @param unit (see Nanook::Account#balance)
|
265
|
+
#
|
183
266
|
# @return [Hash{Symbol=>String}] information about the synchronizing blocks for this node
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
267
|
+
# @raise [Nanook::NanoUnitError] if `unit` is invalid
|
268
|
+
def synchronizing_blocks(limit: 1000, unit: Nanook.default_unit)
|
269
|
+
validate_unit!(unit)
|
270
|
+
|
271
|
+
params = {
|
272
|
+
count: limit,
|
273
|
+
json_block: true,
|
274
|
+
_access: :blocks,
|
275
|
+
_coerce: Hash
|
276
|
+
}
|
277
|
+
|
278
|
+
response = rpc(:unchecked, params).map do |block, info|
|
279
|
+
info[:account] = as_account(info[:account]) if info[:account]
|
280
|
+
info[:link_as_account] = as_account(info[:link_as_account]) if info[:link_as_account]
|
281
|
+
info[:representative] = as_account(info[:representative]) if info[:representative]
|
282
|
+
info[:previous] = as_block(info[:previous]) if info[:previous]
|
283
|
+
info[:link] = as_block(info[:link]) if info[:link]
|
284
|
+
info[:balance] = raw_to_NANO(info[:balance]) if unit == :nano && info[:balance]
|
285
|
+
|
286
|
+
[as_block(block), info]
|
188
287
|
end
|
189
|
-
|
288
|
+
|
289
|
+
Hash[response]
|
190
290
|
end
|
191
|
-
|
291
|
+
alias unchecked synchronizing_blocks
|
192
292
|
|
193
293
|
# The percentage completeness of the synchronization process for
|
194
294
|
# your node as it downloads the nano ledger. Note, it's normal for
|
@@ -199,35 +299,70 @@ class Nanook
|
|
199
299
|
# @return [Float] the percentage completeness of the synchronization
|
200
300
|
# process for your node
|
201
301
|
def sync_progress
|
202
|
-
response = rpc(:block_count)
|
302
|
+
response = rpc(:block_count, _coerce: Hash)
|
203
303
|
|
204
304
|
count = response[:count]
|
205
305
|
unchecked = response[:unchecked]
|
206
|
-
total =
|
306
|
+
total = count + unchecked
|
207
307
|
|
208
308
|
count.to_f * 100 / total.to_f
|
209
309
|
end
|
210
310
|
|
211
|
-
#
|
212
|
-
#
|
311
|
+
# Returns node uptime in seconds
|
312
|
+
#
|
313
|
+
# @return [Integer] seconds of uptime
|
314
|
+
def uptime
|
315
|
+
rpc(:uptime, _access: :seconds, _coerce: Hash)
|
316
|
+
end
|
317
|
+
|
318
|
+
# Sets the receive minimum for wallets on the node. The value is in +Nano+ by default.
|
319
|
+
# To specify an amount in +raw+, pass the argument +unit: :raw+.
|
320
|
+
#
|
321
|
+
# ==== Example:
|
322
|
+
#
|
323
|
+
# account.change_receive_minimum(0.01) # true
|
213
324
|
#
|
214
|
-
# @return [Boolean]
|
215
|
-
|
216
|
-
|
217
|
-
|
325
|
+
# @return [Boolean] true if the action was successful
|
326
|
+
# @param minimum Amount to set as the receive minimum
|
327
|
+
# @param unit optional. Specify +raw+ if you want to set the amount in +raw+. (See Nanook::Account#balance)
|
328
|
+
# @raise [Nanook::NanoUnitError] if `unit` is invalid
|
329
|
+
def change_receive_minimum(minimum, unit: Nanook.default_unit)
|
330
|
+
validate_unit!(unit)
|
331
|
+
|
332
|
+
minimum = NANO_to_raw(minimum) if unit == :nano
|
333
|
+
|
334
|
+
rpc(:receive_minimum_set, amount: minimum).key?(:success)
|
335
|
+
end
|
336
|
+
|
337
|
+
# Returns receive minimum for wallets on the node.
|
338
|
+
#
|
339
|
+
# ==== Example:
|
340
|
+
#
|
341
|
+
# account.receive_minimum # => 0.01
|
342
|
+
#
|
343
|
+
# @return [Integer|Float] the receive minimum
|
344
|
+
# @param unit (see Nanook::Account#balance)
|
345
|
+
# @raise [Nanook::NanoUnitError] if `unit` is invalid
|
346
|
+
def receive_minimum(unit: Nanook.default_unit)
|
347
|
+
validate_unit!(unit)
|
348
|
+
|
349
|
+
amount = rpc(:receive_minimum, _access: :amount)
|
350
|
+
|
351
|
+
return amount unless unit == :nano
|
352
|
+
|
353
|
+
raw_to_NANO(amount)
|
218
354
|
end
|
219
355
|
|
220
356
|
# @return [Hash{Symbol=>Integer|String}] version information for this node
|
221
357
|
def version
|
222
|
-
rpc(:version)
|
358
|
+
rpc(:version, _coerce: Hash)
|
223
359
|
end
|
224
|
-
|
360
|
+
alias info version
|
225
361
|
|
226
362
|
private
|
227
363
|
|
228
|
-
def rpc(action, params={})
|
364
|
+
def rpc(action, params = {})
|
229
365
|
@rpc.call(action, params)
|
230
366
|
end
|
231
|
-
|
232
367
|
end
|
233
368
|
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'util'
|
4
|
+
|
5
|
+
class Nanook
|
6
|
+
# The <tt>Nanook::PrivateKey</tt> class lets you manage your node's keys.
|
7
|
+
class PrivateKey
|
8
|
+
include Nanook::Util
|
9
|
+
|
10
|
+
def initialize(rpc, key = nil)
|
11
|
+
@rpc = rpc
|
12
|
+
@key = key.to_s if key
|
13
|
+
end
|
14
|
+
|
15
|
+
def id
|
16
|
+
@key
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param other [Nanook::PrivateKey] private key to compare
|
20
|
+
# @return [Boolean] true if keys are equal
|
21
|
+
def ==(other)
|
22
|
+
other.class == self.class &&
|
23
|
+
other.id == id
|
24
|
+
end
|
25
|
+
alias eql? ==
|
26
|
+
|
27
|
+
# The hash value is used along with #eql? by the Hash class to determine if two objects
|
28
|
+
# reference the same hash key.
|
29
|
+
#
|
30
|
+
# @return [Integer]
|
31
|
+
def hash
|
32
|
+
id.hash
|
33
|
+
end
|
34
|
+
|
35
|
+
# Generate a new private public key pair. Returns the new {Nanook::PrivateKey}.
|
36
|
+
# The public key can be retrieved by calling `#public_key` on the private key.
|
37
|
+
#
|
38
|
+
# ==== Examples:
|
39
|
+
#
|
40
|
+
# private_key = nanook.private_key.create
|
41
|
+
# private_key.public_key # => Nanook::PublicKey pair for the private key
|
42
|
+
#
|
43
|
+
# deterministic_private_key = nanook.private_key.create(seed: seed, index: 0)
|
44
|
+
#
|
45
|
+
# @param seed [String] optional seed to generate a deterministic private key.
|
46
|
+
# @param index [Integer] optional (but required if +seed+ is given) index to generate a deterministic private key.
|
47
|
+
# @return Nanook::PrivateKey
|
48
|
+
def create(seed: nil, index: nil)
|
49
|
+
skip_key_required!
|
50
|
+
|
51
|
+
params = {
|
52
|
+
_access: :private,
|
53
|
+
_coerce: Hash
|
54
|
+
}
|
55
|
+
|
56
|
+
@key = if seed.nil?
|
57
|
+
rpc(:key_create, params)
|
58
|
+
else
|
59
|
+
raise ArgumentError, 'index argument is required when seed is given' if index.nil?
|
60
|
+
|
61
|
+
rpc(:deterministic_key, params.merge(seed: seed, index: index))
|
62
|
+
end
|
63
|
+
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns the {Nanook::Account} that matches this private key. The
|
68
|
+
# account may not exist yet in the ledger.
|
69
|
+
#
|
70
|
+
# @return Nanook::Account
|
71
|
+
def account
|
72
|
+
as_account(memoized_key_expand[:account])
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns the {Nanook::PublicKey} pair for this private key.
|
76
|
+
#
|
77
|
+
# @return Nanook::PublicKey
|
78
|
+
def public_key
|
79
|
+
as_public_key(memoized_key_expand[:public])
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [String]
|
83
|
+
def to_s
|
84
|
+
"#{self.class.name}(id: \"#{short_id}\")"
|
85
|
+
end
|
86
|
+
alias inspect to_s
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def memoized_key_expand
|
91
|
+
@memoized_key_expand ||= rpc(:key_expand, _coerce: Hash)
|
92
|
+
end
|
93
|
+
|
94
|
+
def rpc(action, params = {})
|
95
|
+
check_key_required!
|
96
|
+
|
97
|
+
p = { key: @key }.compact
|
98
|
+
@rpc.call(action, p.merge(params)).tap { reset_skip_key_required! }
|
99
|
+
end
|
100
|
+
|
101
|
+
def skip_key_required!
|
102
|
+
@skip_key_required_check = true
|
103
|
+
end
|
104
|
+
|
105
|
+
def reset_skip_key_required!
|
106
|
+
@skip_key_required_check = false
|
107
|
+
end
|
108
|
+
|
109
|
+
def check_key_required!
|
110
|
+
return if @key || @skip_key_required_check
|
111
|
+
|
112
|
+
raise ArgumentError, 'Key must be present'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|