zold 0.14.17 → 0.14.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +1 -1
- data/bin/zold +6 -1
- data/fixtures/scripts/_head.sh +33 -13
- data/fixtures/scripts/calculate-scores.sh +1 -1
- data/fixtures/scripts/distribute-wallet.sh +2 -2
- data/fixtures/scripts/push-and-pull.sh +2 -2
- data/fixtures/scripts/redeploy-on-upgrade.sh +3 -3
- data/fixtures/scripts/sigdump.sh +1 -1
- data/fixtures/scripts/spread-wallets.sh +2 -2
- data/lib/zold/commands/next.rb +57 -0
- data/lib/zold/commands/node.rb +23 -8
- data/lib/zold/commands/remote.rb +7 -4
- data/lib/zold/metronome.rb +0 -1
- data/lib/zold/node/farm.rb +3 -2
- data/lib/zold/node/front.rb +11 -0
- data/lib/zold/node/spread_entrance.rb +0 -1
- data/lib/zold/remotes.rb +68 -75
- data/lib/zold/type.rb +5 -4
- data/lib/zold/version.rb +1 -1
- data/test/commands/routines/test_reconnect.rb +2 -2
- data/test/commands/test_create.rb +1 -1
- data/test/commands/test_invoice.rb +1 -1
- data/test/commands/test_list.rb +1 -1
- data/test/commands/test_merge.rb +1 -1
- data/test/commands/test_pull.rb +49 -0
- data/test/commands/test_remote.rb +11 -12
- data/test/commands/test_show.rb +1 -1
- data/test/fake_home.rb +26 -4
- data/test/node/fake_node.rb +1 -0
- data/test/node/test_farm.rb +10 -10
- data/test/test_atomic_file.rb +2 -2
- data/test/test_copies.rb +8 -8
- data/test/test_key.rb +1 -1
- data/test/test_remotes.rb +60 -21
- data/test/test_zold.rb +3 -2
- metadata +5 -4
- data/.zoldata/remotes +0 -1
- data/farm +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5ecca17ec572da83d30874c74ad40b383e22855e
|
4
|
+
data.tar.gz: 501cc2ef09710e3bd6d578d3f8cae99895ae25e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 584cfab45f2f97b6d5bccaee7e285d1637ca546357bb9b08e7a7e30f85789a31ca69e59ec96e72177b6c64568284e0ab982248bc25c0ada8a13ad86580c9496b
|
7
|
+
data.tar.gz: '09553f0034eee231dbba5c6d1eccd3c2d4e8e120bb70505f741d1b20f21cd7d103baeca54259c0e5bfad66b97e4b64b62ea3fb536b5eef411b9344faf83fbbfa'
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/bin/zold
CHANGED
@@ -95,6 +95,8 @@ Available commands:
|
|
95
95
|
Run node at the given TCP port
|
96
96
|
#{Rainbow('alias').green} [alias] [wallet ID]
|
97
97
|
Set an alias for a wallet
|
98
|
+
#{Rainbow('next').green} score
|
99
|
+
Generate next score from the provided one
|
98
100
|
#{Rainbow('score').green} [options]
|
99
101
|
Generate score for the given host and port
|
100
102
|
Available options:"
|
@@ -149,7 +151,7 @@ Available options:"
|
|
149
151
|
Zold::RenameForeignWallets.new(Dir.pwd, opts['network'], log).exec
|
150
152
|
|
151
153
|
wallets = Zold::Wallets.new('.')
|
152
|
-
remotes = Zold::Remotes.new('./.zoldata/remotes', network: opts['network'])
|
154
|
+
remotes = Zold::Remotes.new(file: './.zoldata/remotes', network: opts['network'])
|
153
155
|
copies = './.zoldata/copies'
|
154
156
|
|
155
157
|
case command
|
@@ -201,6 +203,9 @@ Available options:"
|
|
201
203
|
when 'score'
|
202
204
|
require_relative '../lib/zold/commands/calculate'
|
203
205
|
Zold::Calculate.new(log: log).run(args)
|
206
|
+
when 'next'
|
207
|
+
require_relative '../lib/zold/commands/next'
|
208
|
+
Zold::Next.new(log: log).run(args)
|
204
209
|
else
|
205
210
|
raise "Command '#{command}' is not supported"
|
206
211
|
end
|
data/fixtures/scripts/_head.sh
CHANGED
@@ -5,7 +5,7 @@ shopt -s expand_aliases
|
|
5
5
|
|
6
6
|
export RUBYOPT="-W0"
|
7
7
|
|
8
|
-
alias zold="$1 --ignore-this-stupid-option --ignore-global-config --trace --network=test --no-colors"
|
8
|
+
alias zold="$1 --ignore-this-stupid-option --halt-code=test --ignore-global-config --trace --network=test --no-colors --dump-errors"
|
9
9
|
|
10
10
|
function reserve_port {
|
11
11
|
python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'
|
@@ -13,33 +13,53 @@ function reserve_port {
|
|
13
13
|
|
14
14
|
function wait_for_url {
|
15
15
|
while ! curl --silent --fail $1 > /dev/null; do
|
16
|
-
((p++)) || sleep
|
16
|
+
((p++)) || sleep 5
|
17
17
|
if ((p==30)); then
|
18
|
-
echo URL $1 is not available after $p seconds of waiting
|
19
|
-
exit
|
18
|
+
echo "URL $1 is not available after $p seconds of waiting"
|
19
|
+
exit 12
|
20
20
|
fi
|
21
|
-
sleep
|
21
|
+
sleep 5
|
22
22
|
done
|
23
23
|
}
|
24
24
|
|
25
25
|
function wait_for_port {
|
26
26
|
while ! nc -z localhost $1; do
|
27
|
-
((p++)) || sleep
|
27
|
+
((p++)) || sleep 5
|
28
28
|
if ((p==30)); then
|
29
|
-
echo Port $1 is not available after $p seconds of waiting
|
30
|
-
exit
|
29
|
+
echo "Port $1 is not available after $p seconds of waiting"
|
30
|
+
exit 13
|
31
31
|
fi
|
32
|
-
sleep
|
32
|
+
sleep 5
|
33
33
|
done
|
34
34
|
}
|
35
35
|
|
36
36
|
function wait_for_file {
|
37
37
|
while [ ! -f $1 ]; do
|
38
|
-
((c++)) || sleep
|
38
|
+
((c++)) || sleep 5
|
39
39
|
if ((c==30)); then
|
40
|
-
echo File $1 not found, giving up after $c seconds of waiting
|
41
|
-
exit
|
40
|
+
echo "File $1 not found, giving up after $c seconds of waiting"
|
41
|
+
exit 14
|
42
42
|
fi
|
43
|
-
sleep
|
43
|
+
sleep 5
|
44
44
|
done
|
45
45
|
}
|
46
|
+
|
47
|
+
function halt_nodes {
|
48
|
+
for p in "$@"; do
|
49
|
+
pid=$(curl --silent "http://localhost:$p/pid?halt=test" || echo 'absent')
|
50
|
+
if [[ "${pid}" =~ ^[0-9]+$ ]]; then
|
51
|
+
while kill -0 ${pid}; do
|
52
|
+
((c++)) || sleep 5
|
53
|
+
if ((c==30)); then
|
54
|
+
echo "Waiting for process ${pid} to die"
|
55
|
+
exit 15
|
56
|
+
fi
|
57
|
+
echo "Still waiting for process ${pid} to die, cycle no.${c}"
|
58
|
+
sleep 5
|
59
|
+
done
|
60
|
+
echo "Process ${pid} is dead!"
|
61
|
+
fi
|
62
|
+
echo "Node at TCP port ${p} stopped!"
|
63
|
+
done
|
64
|
+
}
|
65
|
+
|
@@ -4,7 +4,7 @@ function start_node {
|
|
4
4
|
port=$(reserve_port)
|
5
5
|
mkdir ${port}
|
6
6
|
cd ${port}
|
7
|
-
zold node --trace --invoice=
|
7
|
+
zold node --trace --invoice=DISTRWALLET@ffffffffffffffff \
|
8
8
|
--host=localhost --port=${port} --bind-port=${port} \
|
9
9
|
--threads=0 --routine-immediately > log.txt &
|
10
10
|
pid=$!
|
@@ -18,7 +18,7 @@ function start_node {
|
|
18
18
|
# don't do the TRAP for killing, the test will never end.
|
19
19
|
first=$(start_node)
|
20
20
|
second=$(start_node)
|
21
|
-
trap "
|
21
|
+
trap "halt_nodes ${first} ${second}" EXIT
|
22
22
|
|
23
23
|
# The first node is linked to the second one and the second one
|
24
24
|
# is linked to the first one. The --home argument specifies their
|
@@ -4,11 +4,11 @@ port=$(reserve_port)
|
|
4
4
|
|
5
5
|
mkdir server
|
6
6
|
cd server
|
7
|
-
zold node --trace --invoice=
|
7
|
+
zold node --trace --invoice=PUSHNPULL@ffffffffffffffff \
|
8
8
|
--host=localhost --port=${port} --bind-port=${port} \
|
9
9
|
--threads=0 --standalone &
|
10
10
|
pid=$!
|
11
|
-
trap "
|
11
|
+
trap "halt_nodes ${port}" EXIT
|
12
12
|
cd ..
|
13
13
|
|
14
14
|
wait_for_port ${port}
|
@@ -4,9 +4,9 @@ function start_node {
|
|
4
4
|
mkdir $1
|
5
5
|
cd $1
|
6
6
|
zold remote clean
|
7
|
-
zold node $3 --nohup --nohup-command='touch restarted' --nohup-log=log \
|
7
|
+
zold node $3 --nohup --nohup-command='touch restarted' --nohup-log=log --nohup-max-cycles=0 \
|
8
8
|
--expose-version=$2 --save-pid=pid --routine-immediately \
|
9
|
-
--verbose --trace --invoice=
|
9
|
+
--verbose --trace --invoice=REDEPLOY@ffffffffffffffff \
|
10
10
|
--host=localhost --port=$1 --bind-port=$1 --threads=0 > /dev/null 2>&1
|
11
11
|
wait_for_port $1
|
12
12
|
cat pid
|
@@ -20,7 +20,7 @@ low=$(reserve_port)
|
|
20
20
|
secondary=$(start_node ${low} 1.1.1)
|
21
21
|
zold remote add localhost ${high} --home=${low} --skip-ping
|
22
22
|
|
23
|
-
trap "
|
23
|
+
trap "halt_nodes ${high}" EXIT
|
24
24
|
|
25
25
|
wait_for_file ${low}/restarted
|
26
26
|
|
data/fixtures/scripts/sigdump.sh
CHANGED
@@ -4,7 +4,7 @@ function start_node {
|
|
4
4
|
port=$(reserve_port)
|
5
5
|
mkdir ${port}
|
6
6
|
cd ${port}
|
7
|
-
zold node --trace --invoice=
|
7
|
+
zold node --trace --invoice=SPREADWALLETS@ffffffffffffffff \
|
8
8
|
--host=localhost --port=${port} --bind-port=${port} \
|
9
9
|
--threads=0 > log.txt &
|
10
10
|
pid=$!
|
@@ -16,7 +16,7 @@ function start_node {
|
|
16
16
|
|
17
17
|
first=$(start_node)
|
18
18
|
second=$(start_node)
|
19
|
-
trap "
|
19
|
+
trap "halt_nodes ${first} ${second}" EXIT
|
20
20
|
|
21
21
|
zold --home=${first} remote clean
|
22
22
|
zold --home=${first} remote add localhost ${second}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2018 Yegor Bugayenko
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the 'Software'), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
13
|
+
# copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
# SOFTWARE.
|
22
|
+
|
23
|
+
require 'slop'
|
24
|
+
require_relative '../log'
|
25
|
+
require_relative '../score'
|
26
|
+
|
27
|
+
# NEXT command.
|
28
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
29
|
+
# Copyright:: Copyright (c) 2018 Yegor Bugayenko
|
30
|
+
# License:: MIT
|
31
|
+
module Zold
|
32
|
+
# Calculate next score
|
33
|
+
class Next
|
34
|
+
def initialize(log: Log::Quiet.new)
|
35
|
+
@log = log
|
36
|
+
end
|
37
|
+
|
38
|
+
def run(args = [])
|
39
|
+
opts = Slop.parse(args, help: true, suppress_errors: true) do |o|
|
40
|
+
o.banner = "Usage: zold next [options] score
|
41
|
+
Available options:"
|
42
|
+
o.bool '--help', 'Print instructions'
|
43
|
+
end
|
44
|
+
if opts.help?
|
45
|
+
@log.info(opts.to_s)
|
46
|
+
return
|
47
|
+
end
|
48
|
+
calculate(opts)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def calculate(opts)
|
54
|
+
@log.info(Score.parse(opts.arguments[1]).next.to_s)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/zold/commands/node.rb
CHANGED
@@ -93,6 +93,9 @@ module Zold
|
|
93
93
|
o.string '--nohup-log',
|
94
94
|
'The file to log output into (default: zold.log)',
|
95
95
|
default: 'zold.log'
|
96
|
+
o.string '--halt-code',
|
97
|
+
'The value of HTTP query parameter "halt," which will cause the front-end immediate termination',
|
98
|
+
default: ''
|
96
99
|
o.string '--save-pid',
|
97
100
|
'The file to save process ID into right after start (only in NOHUP mode)'
|
98
101
|
o.bool '--never-reboot',
|
@@ -111,6 +114,10 @@ module Zold
|
|
111
114
|
"The name of the network (default: #{Wallet::MAIN_NETWORK})",
|
112
115
|
require: true,
|
113
116
|
default: Wallet::MAIN_NETWORK
|
117
|
+
o.integer '--nohup-max-cycles',
|
118
|
+
'Maximum amount of nohup re-starts (-1 by default, which means forever)',
|
119
|
+
require: true,
|
120
|
+
default: -1
|
114
121
|
o.bool '--help', 'Print instructions'
|
115
122
|
end
|
116
123
|
if opts.help?
|
@@ -129,6 +136,7 @@ module Zold
|
|
129
136
|
Front.set(:version, opts['expose-version'])
|
130
137
|
Front.set(:protocol, Zold::PROTOCOL)
|
131
138
|
Front.set(:logging, @log.debug?)
|
139
|
+
Front.set(:halt, opts['halt-code'])
|
132
140
|
Front.set(:home, Dir.pwd)
|
133
141
|
@log.info("Home directory: #{Dir.pwd}")
|
134
142
|
@log.info("Ruby version: #{RUBY_VERSION}")
|
@@ -145,7 +153,7 @@ module Zold
|
|
145
153
|
AccessLog: []
|
146
154
|
)
|
147
155
|
if opts['standalone']
|
148
|
-
@remotes = Remotes::Empty.new
|
156
|
+
@remotes = Zold::Remotes::Empty.new(file: '/tmp/standalone')
|
149
157
|
@log.debug('Running in standalone mode! (will never talk to other remotes)')
|
150
158
|
end
|
151
159
|
Front.set(:ignore_score_weakness, opts['ignore-score-weakness'])
|
@@ -196,10 +204,10 @@ module Zold
|
|
196
204
|
@log.info("Starting up the web front at http://#{host}:#{opts[:port]}...")
|
197
205
|
Front.run!
|
198
206
|
@log.info("The web front stopped at http://#{host}:#{opts[:port]}")
|
207
|
+
@log.info('Thanks for helping Zold network!')
|
199
208
|
end
|
200
209
|
end
|
201
210
|
end
|
202
|
-
@log.info("The node #{host}:#{opts[:port]} is shut down, thanks for helping Zold network!")
|
203
211
|
end
|
204
212
|
|
205
213
|
private
|
@@ -238,19 +246,26 @@ module Zold
|
|
238
246
|
end
|
239
247
|
myself = File.expand_path($PROGRAM_NAME)
|
240
248
|
args = ARGV.delete_if { |a| a.start_with?('--nohup', '--home') }
|
249
|
+
cycle = 0
|
241
250
|
loop do
|
242
251
|
begin
|
243
252
|
code = exec("#{myself} #{args.join(' ')}", nohup_log)
|
244
|
-
if code != 0
|
245
|
-
nohup_log.print("Let's wait for a minute, because of the failure...")
|
246
|
-
sleep(60)
|
247
|
-
end
|
253
|
+
raise "Exit code is #{code}" if code != 0
|
248
254
|
exec(opts['nohup-command'], nohup_log)
|
249
255
|
rescue StandardError => e
|
250
256
|
nohup_log.print(Backtrace.new(e).to_s)
|
251
|
-
|
252
|
-
|
257
|
+
if cycle < opts['nohup-max-cycles']
|
258
|
+
nohup_log.print("Let's wait for a minutes, because of the exception...")
|
259
|
+
sleep(60)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
next if opts['nohup-max-cycles'].negative?
|
263
|
+
cycle += 1
|
264
|
+
if cycle > opts['nohup-max-cycles']
|
265
|
+
nohup_log.print("There are no more nohup cycles left, after the cycle no.#{cycle}")
|
266
|
+
break
|
253
267
|
end
|
268
|
+
nohup_log.print("Going for nohup cycle no.#{cycle}")
|
254
269
|
end
|
255
270
|
end
|
256
271
|
Process.detach(pid)
|
data/lib/zold/commands/remote.rb
CHANGED
@@ -207,10 +207,13 @@ Available options:"
|
|
207
207
|
end
|
208
208
|
|
209
209
|
def trim(opts)
|
210
|
-
@remotes.all
|
211
|
-
|
210
|
+
all = @remotes.all
|
211
|
+
all.each do |r|
|
212
|
+
next if r[:errors] <= opts['tolerate']
|
213
|
+
remove(r[:host], r[:port], opts)
|
214
|
+
@log.info("#{r[:host]}:#{r[:port]} removed because of #{r[:errors]} errors (over #{opts['tolerate']})")
|
212
215
|
end
|
213
|
-
@log.info("The list of remotes trimmed, #{@remotes.all.count} nodes left there")
|
216
|
+
@log.info("The list of #{all.count} remotes trimmed, #{@remotes.all.count} nodes left there")
|
214
217
|
end
|
215
218
|
|
216
219
|
def update(opts, deep = true)
|
@@ -256,7 +259,7 @@ in #{(Time.now - start).round(2)}s")
|
|
256
259
|
end
|
257
260
|
|
258
261
|
def select(opts)
|
259
|
-
selected = @remotes.all.sort_by { |r| r[:score] }.first(opts['max-nodes'])
|
262
|
+
selected = @remotes.all.sort_by { |r| r[:score] }.reverse.first(opts['max-nodes'])
|
260
263
|
(@remotes.all - selected).each do |r|
|
261
264
|
@remotes.remove(r[:host], r[:port])
|
262
265
|
end
|
data/lib/zold/metronome.rb
CHANGED
data/lib/zold/node/farm.rb
CHANGED
@@ -78,7 +78,6 @@ module Zold
|
|
78
78
|
Thread.new do
|
79
79
|
Thread.current.abort_on_exception = true
|
80
80
|
Thread.current.name = "f#{t}"
|
81
|
-
Thread.current.priority = -100
|
82
81
|
loop do
|
83
82
|
VerboseThread.new(@log).run do
|
84
83
|
cycle(host, port, strength, threads)
|
@@ -90,7 +89,6 @@ module Zold
|
|
90
89
|
@cleanup = Thread.new do
|
91
90
|
Thread.current.abort_on_exception = true
|
92
91
|
Thread.current.name = 'cleanup'
|
93
|
-
Thread.current.priority = -100
|
94
92
|
while alive
|
95
93
|
sleep(60) unless strength == 1 # which will only happen in tests
|
96
94
|
VerboseThread.new(@log).run do
|
@@ -147,6 +145,9 @@ module Zold
|
|
147
145
|
return unless s.strength >= strength
|
148
146
|
Thread.current.name = s.to_mnemo
|
149
147
|
save(threads, [s.next])
|
148
|
+
# score = Score.parse(`ruby #{File.join(File.dirname(__FILE__), '../../../bin/zold')} next "#{s}"`)
|
149
|
+
# @log.debug("New score discovered: #{score}")
|
150
|
+
# save(threads, [score])
|
150
151
|
cleanup(host, port, strength, threads)
|
151
152
|
end
|
152
153
|
|
data/lib/zold/node/front.rb
CHANGED
@@ -49,6 +49,7 @@ module Zold
|
|
49
49
|
set :lock, false
|
50
50
|
set :show_exceptions, false
|
51
51
|
set :server, 'webrick'
|
52
|
+
set :halt, '' # to be injected at node.rb
|
52
53
|
set :dump_errors, false # to be injected at node.rb
|
53
54
|
set :version, VERSION # to be injected at node.rb
|
54
55
|
set :protocol, PROTOCOL # to be injected at node.rb
|
@@ -69,6 +70,10 @@ module Zold
|
|
69
70
|
use Rack::Deflater
|
70
71
|
|
71
72
|
before do
|
73
|
+
if !settings.halt.empty? && params[:halt] && params[:halt] == settings.halt
|
74
|
+
settings.log.error('Halt signal received, shutting the front end down...')
|
75
|
+
Front.stop!
|
76
|
+
end
|
72
77
|
check_header(Http::NETWORK_HEADER) do |header|
|
73
78
|
if header != settings.network
|
74
79
|
raise "Network name mismatch at #{request.url}, #{request.ip} is in '#{header}', \
|
@@ -119,6 +124,11 @@ while #{settings.address} is in '#{settings.network}'"
|
|
119
124
|
settings.version
|
120
125
|
end
|
121
126
|
|
127
|
+
get '/pid' do
|
128
|
+
content_type 'text/plain'
|
129
|
+
Process.pid.to_s
|
130
|
+
end
|
131
|
+
|
122
132
|
get '/score' do
|
123
133
|
content_type 'text/plain'
|
124
134
|
score.to_s
|
@@ -143,6 +153,7 @@ while #{settings.address} is in '#{settings.network}'"
|
|
143
153
|
score: score.to_h,
|
144
154
|
pid: Process.pid,
|
145
155
|
cpus: Concurrent.processor_count,
|
156
|
+
platform: RUBY_PLATFORM,
|
146
157
|
uptime: `uptime`.strip,
|
147
158
|
threads: "#{Thread.list.select { |t| t.status == 'run' }.count}/#{Thread.list.count}",
|
148
159
|
wallets: settings.wallets.all.count,
|