zold-stress 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ Feature: Command Line Processing
2
+ As an payment originator I want to be able to use
3
+ Zold as a command line tool
4
+
5
+ Scenario: Help can be printed
6
+ When I run bin/zold-stress with "-h"
7
+ Then Exit code is zero
8
+ And Stdout contains "--help"
@@ -0,0 +1,26 @@
1
+ Feature: Gem Package
2
+ As a source code writer I want to be able to
3
+ package the Gem into .gem file
4
+
5
+ Scenario: Gem can be packaged
6
+ Given It is Unix
7
+ Given I have "execs.rb" file with content:
8
+ """
9
+ #!/usr/bin/env ruby
10
+ require 'rubygems'
11
+ spec = Gem::Specification::load('./spec.rb')
12
+ if spec.executables.empty?
13
+ fail 'no executables: ' + IO.read('./spec.rb')
14
+ end
15
+ """
16
+ When I run bash with:
17
+ """
18
+ set -x
19
+ set -e
20
+ cd zold
21
+ gem build zold-stress.gemspec
22
+ gem specification --ruby zold-stress-*.gem > ../spec.rb
23
+ cd ..
24
+ ruby execs.rb
25
+ """
26
+ Then Exit code is zero
@@ -0,0 +1,84 @@
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 'tmpdir'
24
+ require 'slop'
25
+ require 'English'
26
+ require 'zold'
27
+
28
+ Before do
29
+ @cwd = Dir.pwd
30
+ @dir = Dir.mktmpdir('test')
31
+ FileUtils.copy('fixtures/id_rsa', @dir)
32
+ FileUtils.copy('fixtures/id_rsa.pub', @dir)
33
+ FileUtils.mkdir_p(@dir) unless File.exist?(@dir)
34
+ Dir.chdir(@dir)
35
+ end
36
+
37
+ After do
38
+ Dir.chdir(@cwd)
39
+ FileUtils.rm_rf(@dir) if File.exist?(@dir)
40
+ end
41
+
42
+ When(%r{^I run ([a-z/-]+) with "([^"]*)"$}) do |cmd, args|
43
+ home = File.expand_path(File.join(File.dirname(__FILE__), '../..'))
44
+ @stdout = `ruby -I#{home}/lib #{home}/#{cmd} #{args} 2>&1`
45
+ @exitstatus = $CHILD_STATUS.exitstatus
46
+ end
47
+
48
+ When(/^I run bash with:$/) do |text|
49
+ FileUtils.copy_entry(@cwd, File.join(@dir, 'zold'))
50
+ IO.write('run.sh', text)
51
+ @stdout = `/bin/bash run.sh 2>&1`
52
+ @exitstatus = $CHILD_STATUS.exitstatus
53
+ end
54
+
55
+ When(/^I have "([^"]*)" file with content:$/) do |file, text|
56
+ FileUtils.mkdir_p(File.dirname(file)) unless File.exist?(file)
57
+ File.open(file, 'w:ASCII-8BIT') do |f|
58
+ f.write(text.gsub(/\\xFF/, 0xFF.chr))
59
+ end
60
+ end
61
+
62
+ Then(/^Stdout contains "([^"]*)"$/) do |txt|
63
+ raise "STDOUT doesn't contain '#{txt}':\n#{@stdout}" unless @stdout.include?(txt)
64
+ end
65
+
66
+ Then(/^Stdout is empty$/) do
67
+ raise "STDOUT is not empty:\n#{@stdout}" unless @stdout == ''
68
+ end
69
+
70
+ Then(/^Exit code is zero$/) do
71
+ raise "Non-zero exit #{@exitstatus}:\n#{@stdout}" unless @exitstatus.zero?
72
+ end
73
+
74
+ Then(/^Exit code is not zero$/) do
75
+ raise 'Zero exit code' if @exitstatus.zero?
76
+ end
77
+
78
+ Given(/^It is Unix$/) do
79
+ pending if Gem.win_platform?
80
+ end
81
+
82
+ Given(/^It is Windows$/) do
83
+ pending unless Gem.win_platform?
84
+ end
@@ -0,0 +1,26 @@
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
+ ENV['RACK_ENV'] = 'test'
24
+
25
+ require 'simplecov'
26
+ require 'zold'
@@ -0,0 +1,51 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ 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
51
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1 @@
1
+ ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDVDiAuWyzRxmrB/CRR49N+tHld/EGZ7eoBDivs8X9dH3FfeLvtyNIvdSaQD2NRpjoZQCO3pOpjKVZEuswAnwOLl7M391GX/pmDUXqucXqyX/nx+Py0Xf1zgsOq0ttpnt4OGMBCW1Nf/kbtxL9/MZg9ap6tKsBepBVlBhe4WbVDpHR2kN406SkkhT3GKjaWwIYuQV0uXUkLXqiOGiaOafl2/gRPULffS2g/pnUfQCdej8pFjSxw8LtnQZCOym3OsG/Wd6VgZoSAOUMTRg/6NzEKMw3Q+MxkJEs+KafDa5h0cg1/IRyjnQ9QP5JMJKTZX9BegMp2uCek+o8S+GN6oF9XktUBayJEenuWzlTiyHhmMF9OFnnSFue6Xc5zLHinWoGSKrB0bBhDM1DZy8kuZNoFz+ItfJ1pMKrwQ/gXmvgBK1BXuA+a5PGC/0kHZqKjf3aF349My8wPFvcqkTWrqXf6u/LgeynOzfaU733uNiaX+e1pxv74i1DJaUrhOaqWHgP1OhRfr2sW3P1ABLJevyOVoc4V6IkJpgy5ne88Y+T71IGutNP2qnBX0ZwiVQzesfyE0Aypcsq9zhwwBfY6vo1RBSRCgAwB3cdiDzG5YaITnakyXa7j0TdWtXQBizPWEfD97R8QrTK6dnZsf6dA9oHKsoK8hblHnKnPfSTQTQMgpw== yegor@be-2.local
@@ -0,0 +1,51 @@
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 NONINFRINGEMENT. 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
+ # Payments still flying in air.
24
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
25
+ # Copyright:: Copyright (c) 2018 Yegor Bugayenko
26
+ # License:: MIT
27
+ module Zold::Stress
28
+ # Flying payments.
29
+ class Air
30
+ def initialize
31
+ @mutex = Mutex.new
32
+ @all = []
33
+ end
34
+
35
+ def fetch
36
+ @all
37
+ end
38
+
39
+ def add(pmt)
40
+ @mutex.synchronize do
41
+ @all << pmt
42
+ end
43
+ end
44
+
45
+ def delete(pmt)
46
+ @mutex.synchronize do
47
+ @all.delete(pmt)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,97 @@
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 NONINFRINGEMENT. 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 'zold/key'
24
+ require 'zold/tax'
25
+ require 'zold/commands/pay'
26
+ require 'zold/commands/remote'
27
+ require 'zold/commands/taxes'
28
+ require_relative 'stats'
29
+
30
+ # Pool of wallets.
31
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
32
+ # Copyright:: Copyright (c) 2018 Yegor Bugayenko
33
+ # License:: MIT
34
+ module Zold::Stress
35
+ # Payments to send in a batch.
36
+ class Pmnts
37
+ def initialize(pvt:, wallets:, remotes:, stats:, opts:,
38
+ vlog: Zold::Log::Quiet.new, log: Zold::Log::Quiet.new)
39
+ @pvt = pvt
40
+ @wallets = wallets
41
+ @remotes = remotes
42
+ @log = log
43
+ @vlog = vlog
44
+ @opts = opts
45
+ @stats = stats
46
+ end
47
+
48
+ def send
49
+ raise 'Too few wallets in the pool' if @wallets.all.count < 2
50
+ paid = []
51
+ all = @wallets.all
52
+ Tempfile.open do |f|
53
+ File.write(f, @pvt.to_s)
54
+ loop do
55
+ source = all.sample
56
+ balance = @wallets.find(source, &:balance)
57
+ next if balance.negative? || balance.zero?
58
+ amount = balance / all.count
59
+ next if amount < Zold::Amount.new(zld: 0.0001)
60
+ loop do
61
+ target = all.sample
62
+ next if source == target
63
+ paid << pay_one(source, target, amount, f.path)
64
+ break
65
+ end
66
+ break if paid.count >= @opts['batch']
67
+ end
68
+ end
69
+ paid
70
+ end
71
+
72
+ private
73
+
74
+ def pay_one(source, target, amount, pvt)
75
+ Zold::Taxes.new(wallets: @wallets, remotes: @remotes, log: @vlog).run(
76
+ [
77
+ 'taxes', 'pay', source.to_s, "--network=#{@opts['network']}",
78
+ "--private-key=#{pvt}", '--ignore-nodes-absence'
79
+ ]
80
+ )
81
+ if @wallets.find(source) { |w| Zold::Tax.new(w).in_debt? }
82
+ @log.error("The wallet #{source} is in debt and we can't pay taxes")
83
+ return
84
+ end
85
+ details = SecureRandom.uuid
86
+ @stats.exec('paid', swallow: false) do
87
+ Zold::Pay.new(wallets: @wallets, remotes: @remotes, log: @vlog).run(
88
+ [
89
+ 'pay', source.to_s, target.to_s, amount.to_zld(6), details,
90
+ "--network=#{@opts['network']}", "--private-key=#{pvt}"
91
+ ]
92
+ )
93
+ end
94
+ { start: Time.now, source: source, target: target, amount: amount, details: details }
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,74 @@
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 NONINFRINGEMENT. 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 'zold/log'
24
+ require 'zold/id'
25
+ require 'zold/key'
26
+ require 'zold/amount'
27
+ require 'zold/commands/create'
28
+ require 'zold/commands/pull'
29
+ require 'zold/commands/remove'
30
+ require 'parallelize'
31
+ require_relative 'stats'
32
+
33
+ # Pool of wallets.
34
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
35
+ # Copyright:: Copyright (c) 2018 Yegor Bugayenko
36
+ # License:: MIT
37
+ module Zold::Stress
38
+ # Pool of wallets.
39
+ class Pool
40
+ def initialize(wallets:, remotes:, copies:, stats:, opts:,
41
+ log: Zold::Log::Quiet.new, vlog: Zold::Log::Quiet.new)
42
+ @wallets = wallets
43
+ @remotes = remotes
44
+ @copies = copies
45
+ @log = log
46
+ @vlog = vlog
47
+ @opts = opts
48
+ @stats = stats
49
+ end
50
+
51
+ def rebuild
52
+ raise "There are no wallets in the pool at #{@wallets.path}, at least one is needed" if @wallets.all.empty?
53
+ balances = @wallets.all
54
+ .map { |id| { id: id, balance: @wallets.find(id, &:balance) } }
55
+ .sort_by { |h| h[:balance] }
56
+ .reverse
57
+ balances.last([balances.count - @opts['pool'], 0].max).each do |h|
58
+ Zold::Remove.new(wallets: @wallets, log: @vlog).run(
59
+ ['remove', h[:id].to_s]
60
+ )
61
+ end
62
+ Tempfile.open do |f|
63
+ File.write(f, @wallets.find(balances[0][:id], &:key).to_s)
64
+ while @wallets.all.count < @opts['pool']
65
+ Zold::Create.new(wallets: @wallets, log: @vlog).run(
66
+ ['create', "--public-key=#{f.path}", "--network=#{@opts['network']}"] + @opts.arguments
67
+ )
68
+ end
69
+ end
70
+ return unless balances[0][:balance].zero?
71
+ raise "There is no money in the pool of #{balances.count} wallets at #{@wallets.path}"
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,144 @@
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 NONINFRINGEMENT. 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 'backtrace'
24
+ require 'parallelize'
25
+ require 'zold/key'
26
+ require 'zold/id'
27
+ require 'zold/commands/push'
28
+ require 'zold/commands/remote'
29
+ require_relative 'stats'
30
+ require_relative 'pool'
31
+ require_relative 'pmnts'
32
+ require_relative 'air'
33
+
34
+ # Stress test.
35
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
36
+ # Copyright:: Copyright (c) 2018 Yegor Bugayenko
37
+ # License:: MIT
38
+ module Zold::Stress
39
+ # Full round of stress test
40
+ class Round
41
+ def initialize(pvt:, wallets:, remotes:, copies:,
42
+ stats:, air:, opts:, log: Zold::Log::Quiet.new, vlog: Zold::Log::Quiet.new)
43
+ @pvt = pvt
44
+ @wallets = wallets
45
+ @remotes = remotes
46
+ @copies = copies
47
+ @opts = opts
48
+ @log = log
49
+ @stats = stats
50
+ @air = air
51
+ @vlog = vlog
52
+ end
53
+
54
+ def update
55
+ start = Time.now
56
+ cmd = Zold::Remote.new(remotes: @remotes, log: @vlog)
57
+ args = ['remote'] + @opts.arguments
58
+ cmd.run(args + ['trim'])
59
+ cmd.run(args + ['reset']) if @remotes.all.empty?
60
+ @stats.exec('update') do
61
+ cmd.run(args + ['update'])
62
+ end
63
+ cmd.run(args + ['select'])
64
+ @log.info("List of remotes updated in #{Zold::Age.new(start)}, #{@remotes.all.count} nodes in the list")
65
+ end
66
+
67
+ def prepare
68
+ start = Time.now
69
+ pool = Zold::Stress::Pool.new(
70
+ wallets: @wallets,
71
+ remotes: @remotes, copies: @copies, stats: @stats,
72
+ log: @log, opts: @opts, vlog: @vlog
73
+ )
74
+ pool.rebuild
75
+ @wallets.all.peach(@opts['threads']) do |id|
76
+ @stats.exec('push') do
77
+ Zold::Push.new(wallets: @wallets, remotes: @remotes, log: @vlog).run(
78
+ ['push', id.to_s, "--network=#{@opts['network']}"] + @opts.arguments
79
+ )
80
+ end
81
+ end
82
+ @log.info("There are #{@wallets.all.count} wallets in the pool \
83
+ with #{@wallets.all.map { |id| @wallets.find(id, &:balance) }.inject(&:+)} \
84
+ in #{Zold::Age.new(start)}")
85
+ end
86
+
87
+ def send
88
+ start = Time.now
89
+ sent = Zold::Stress::Pmnts.new(
90
+ pvt: @pvt, wallets: @wallets,
91
+ remotes: @remotes, stats: @stats,
92
+ log: @log, opts: @opts, vlog: @vlog
93
+ ).send
94
+ mutex = Mutex.new
95
+ sources = sent.group_by { |p| p[:source] }
96
+ sources.peach(@opts['threads']) do |a|
97
+ @stats.exec('push') do
98
+ Zold::Push.new(wallets: @wallets, remotes: @remotes, log: @vlog).run(
99
+ ['push', a[0].to_s, "--network=#{@opts['network']}"] + @opts.arguments
100
+ )
101
+ mutex.synchronize do
102
+ a[1].each { |p| @air.add(p) }
103
+ end
104
+ end
105
+ end
106
+ @log.info("#{sent.count} payments sent from #{sources.count} wallets, \
107
+ in #{Zold::Age.new(start)}, #{@air.fetch.count} are now in the air:
108
+ #{sent.map { |p| "#{p[:source]} -> #{p[:target]} #{p[:amount]}" }.join("\n ")}")
109
+ end
110
+
111
+ def pull
112
+ start = Time.now
113
+ targets = @air.fetch.group_by { |p| p[:target] }.map { |a| a[0] }
114
+ targets.each do |id|
115
+ next unless @wallets.find(id, &:exists?)
116
+ Zold::Remove.new(wallets: @wallets, log: @vlog).run(
117
+ ['remove', id.to_s]
118
+ )
119
+ end
120
+ targets.peach(@opts['threads']) do |id|
121
+ @stats.exec('pull') do
122
+ Zold::Pull.new(wallets: @wallets, remotes: @remotes, copies: @copies, log: @vlog).run(
123
+ ['pull', id.to_s, "--network=#{@opts['network']}"] + @opts.arguments
124
+ )
125
+ end
126
+ end
127
+ @log.info("There are #{@wallets.all.count} wallets left, \
128
+ after the pull of #{targets.count} in #{Zold::Age.new(start)}")
129
+ end
130
+
131
+ def match
132
+ @air.fetch.each do |p|
133
+ next unless @wallets.find(p[:target], &:exists?)
134
+ t = @wallets.find(p[:target], &:txns).find { |x| x.details == p[:details] && x.bnf == p[:source] }
135
+ next if t.nil?
136
+ @stats.put('arrived', Time.now - p[:start])
137
+ @log.info("#{p[:amount]} arrived from #{p[:source]} to #{p[:target]} \
138
+ in txn ##{t.id} in #{Zold::Age.new(p[:start])}: #{t.details}")
139
+ @air.delete(p)
140
+ end
141
+ @log.info("#{@air.fetch.count} payments are still in the air")
142
+ end
143
+ end
144
+ end