zold 0.5 → 0.6

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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -2
  3. data/bin/zold +34 -48
  4. data/features/cli.feature +1 -1
  5. data/features/step_definitions/steps.rb +1 -3
  6. data/features/support/env.rb +2 -0
  7. data/fixtures/scripts/push-and-pull.sh +6 -4
  8. data/lib/zold/amount.rb +17 -3
  9. data/lib/zold/commands/create.rb +9 -6
  10. data/lib/zold/commands/fetch.rb +11 -21
  11. data/lib/zold/commands/node.rb +7 -9
  12. data/lib/zold/commands/pay.rb +9 -6
  13. data/lib/zold/commands/propagate.rb +4 -5
  14. data/lib/zold/commands/push.rb +10 -5
  15. data/lib/zold/commands/remote.rb +22 -24
  16. data/lib/zold/commands/show.rb +1 -2
  17. data/lib/zold/commands/taxes.rb +154 -0
  18. data/lib/zold/http.rb +1 -3
  19. data/lib/zold/key.rb +1 -3
  20. data/lib/zold/node/entrance.rb +8 -3
  21. data/lib/zold/node/farm.rb +8 -6
  22. data/lib/zold/node/front.rb +0 -1
  23. data/lib/zold/patch.rb +1 -1
  24. data/lib/zold/remotes.rb +4 -0
  25. data/lib/zold/score.rb +85 -10
  26. data/lib/zold/signature.rb +7 -7
  27. data/lib/zold/tax.rb +79 -0
  28. data/lib/zold/txn.rb +12 -7
  29. data/lib/zold/version.rb +1 -1
  30. data/lib/zold/wallet.rb +2 -2
  31. data/test/commands/test_create.rb +3 -4
  32. data/test/commands/test_diff.rb +2 -3
  33. data/test/commands/test_merge.rb +4 -6
  34. data/test/commands/test_pay.rb +7 -5
  35. data/test/commands/test_remote.rb +5 -3
  36. data/test/commands/test_taxes.rb +66 -0
  37. data/test/node/fake_node.rb +1 -0
  38. data/test/node/test_farm.rb +2 -1
  39. data/test/node/test_front.rb +1 -0
  40. data/test/test__helper.rb +2 -0
  41. data/test/test_remotes.rb +0 -1
  42. data/test/test_score.rb +40 -21
  43. data/test/test_signature.rb +6 -3
  44. data/test/test_tax.rb +53 -0
  45. data/test/test_txn.rb +46 -0
  46. data/test/test_wallet.rb +2 -2
  47. data/test/test_zold.rb +1 -1
  48. data/wp/.gitignore +6 -0
  49. data/wp/wp.tex +38 -0
  50. data/zold.gemspec +1 -3
  51. metadata +12 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 28d39fb05a13cc3dc2a6856289b717c137e751dc
4
- data.tar.gz: 42b2b142eb06e8f360b4d2f311bcf8c86aec7395
3
+ metadata.gz: 01a2323174e903dabb35708bfd1d0f756e930035
4
+ data.tar.gz: a4f6582b8e645cd480ab74e374cc5cefe3698034
5
5
  SHA512:
6
- metadata.gz: 63424ca129a20ac442879e53255572e885c952c32d10bc5b5cddaaca33a04d3698cc0c8114f8b79479988295df8c5bbe6f4bbd65a7c7752d1cbc031768272e68
7
- data.tar.gz: f91e1c18246b1dd7f6fe790ac71a43e4cbdf0c2eae9a718229e7cc4004aea419352986f3b1036ff750200ed40be1aed1a21e9c82aba40de67529d091bbf2dbdd
6
+ metadata.gz: 8e6c51f38a8f4d44c86c6cf1d2c02d3db2a1495504613c29676922a91bc03b325524b87d592dabdeb406fc9ca6906725de8666b805ea98731a7fe4271c950123
7
+ data.tar.gz: '0906447919a3f02950152613a65b19d648ca46cf480405659751ab6ab4eee1d08b01f8f1dfef2a60de4c5e55b17433913637daf37c0d7451f13e8320aa33c572'
data/.rubocop.yml CHANGED
@@ -14,7 +14,7 @@ Layout/MultilineMethodCallIndentation:
14
14
  Metrics/AbcSize:
15
15
  Enabled: false
16
16
  Metrics/BlockLength:
17
- Max: 50
17
+ Max: 100
18
18
  Metrics/ClassLength:
19
19
  Max: 200
20
20
  Style/EndOfLine:
@@ -24,4 +24,6 @@ Metrics/ParameterLists:
24
24
  Layout/AlignParameters:
25
25
  Enabled: false
26
26
  Metrics/PerceivedComplexity:
27
- Max: 12
27
+ Max: 15
28
+ Metrics/LineLength:
29
+ Max: 120
data/bin/zold CHANGED
@@ -41,14 +41,16 @@ Encoding.default_internal = Encoding::UTF_8
41
41
  log = Zold::Log.new
42
42
 
43
43
  args = []
44
- config = File.expand_path('~/.zold')
45
- if File.exist?(config)
46
- body = File.read(config)
47
- extra = body.split(/[\r\n]+/).map(&:strip)
48
- args += extra
49
- log.debug("Found #{body.split(/\n/).length} lines in #{config}")
50
- else
51
- log.debug("Default config file #{config} not found")
44
+ unless ENV['RACK_ENV'] == 'test'
45
+ config = File.expand_path('~/.zold')
46
+ if File.exist?(config)
47
+ body = File.read(config)
48
+ extra = body.split(/[\r\n]+/).map(&:strip)
49
+ args += extra
50
+ log.debug("Found #{body.split(/\n/).length} lines in #{config}")
51
+ else
52
+ log.debug("Default config file #{config} not found")
53
+ end
52
54
  end
53
55
  args += ARGV
54
56
 
@@ -56,44 +58,38 @@ begin
56
58
  opts = Slop.parse(args, strict: false, suppress_errors: true) do |o|
57
59
  o.banner = "Usage: zold [options] command [arguments]
58
60
  Available commands:
59
- #{Rainbow('remote').green}
61
+ #{Rainbow('remote').green} command [options]
60
62
  Manage remote nodes
61
- #{Rainbow('create').green}
63
+ #{Rainbow('create').green} [options]
62
64
  Creates a new wallet with a random ID
63
- #{Rainbow('fetch').green} [ID...]
65
+ #{Rainbow('fetch').green} [ID...] [options]
64
66
  Fetch wallet copies from remote nodes
65
- #{Rainbow('clean').green} [ID...]
67
+ #{Rainbow('clean').green} [ID...] [options]
66
68
  Remove expired local copies
67
- #{Rainbow('merge').green} [ID...]
69
+ #{Rainbow('merge').green} [ID...] [options]
68
70
  Merge remote copies with the HEAD
69
- #{Rainbow('propagate').green} [ID...]
71
+ #{Rainbow('propagate').green} [ID...] [options]
70
72
  Propagate transactions to receiving wallets
71
- #{Rainbow('pull').green} [ID...]
73
+ #{Rainbow('pull').green} [ID...] [options]
72
74
  Fetch and then merge
73
- #{Rainbow('show').green} [ID...]
75
+ #{Rainbow('show').green} [ID...] [options]
74
76
  Show all available information about the wallet
75
- #{Rainbow('pay').green} from to amount details
77
+ #{Rainbow('pay').green} from to amount details [options]
76
78
  Pay ZOLD from one wallet to another
77
- #{Rainbow('invoice').green} ID
79
+ #{Rainbow('invoice').green} ID [options]
78
80
  Generate invoice unique ID for a payment
79
- #{Rainbow('status').green}
80
- Show status of local copies
81
- #{Rainbow('push').green} [ID...]
81
+ #{Rainbow('push').green} [ID...] [options]
82
82
  Push all/some local wallets or the ones required
83
- #{Rainbow('node').green} port
83
+ #{Rainbow('taxes').green} command [ID...] [options]
84
+ Pay taxes, check their status
85
+ #{Rainbow('node').green} [options]
84
86
  Run node at the given TCP port
85
- #{Rainbow('score').green} host port strength
87
+ #{Rainbow('score').green} host port strength [options]
86
88
  Generate score for the given host and port
87
89
  Available options:"
88
90
  o.string '-d', '--dir',
89
91
  'The directory where wallets are stored (default: .)',
90
92
  default: Dir.pwd
91
- o.string '--private-key',
92
- 'The location of RSA private key (default: ~/.ssh/id_rsa)',
93
- default: '~/.ssh/id_rsa'
94
- o.string '--public-key',
95
- 'The location of RSA public key (default: ~/.ssh/id_rsa.pub)',
96
- default: '~/.ssh/id_rsa.pub'
97
93
  o.bool '-h', '--help', 'Show these instructions'
98
94
  o.bool '--trace', 'Show full stack trace in case of a problem'
99
95
  o.on '--no-colors', 'Disable colors in the ouput' do
@@ -129,11 +125,7 @@ Available options:"
129
125
  Zold::Node.new(log: log).run(args)
130
126
  when 'create'
131
127
  require_relative '../lib/zold/commands/create'
132
- Zold::Create.new(
133
- wallets: wallets,
134
- pubkey: Zold::Key.new(file: opts['public-key']),
135
- log: log
136
- ).run(args)
128
+ Zold::Create.new(wallets: wallets, log: log).run(args)
137
129
  when 'remote'
138
130
  require_relative '../lib/zold/commands/remote'
139
131
  Zold::Remote.new(remotes: remotes, log: log).run(args)
@@ -142,11 +134,7 @@ Available options:"
142
134
  Zold::Invoice.new(wallets: wallets, log: log).run(args)
143
135
  when 'pay'
144
136
  require_relative '../lib/zold/commands/pay'
145
- Zold::Pay.new(
146
- wallets: wallets,
147
- pvtkey: Zold::Key.new(file: opts['private-key']),
148
- log: log
149
- ).run(args)
137
+ Zold::Pay.new(wallets: wallets, log: log).run(args)
150
138
  when 'show'
151
139
  require_relative '../lib/zold/commands/show'
152
140
  Zold::Show.new(wallets: wallets, log: log).run(args)
@@ -170,23 +158,21 @@ Available options:"
170
158
  Zold::Fetch.new(remotes: remotes, copies: copies, log: log).run(args)
171
159
  require_relative '../lib/zold/commands/merge'
172
160
  Zold::Merge.new(wallets: wallets, copies: copies, log: log).run(args)
161
+ when 'taxes'
162
+ require_relative '../lib/zold/commands/taxes'
163
+ Zold::Taxes.new(wallets: wallets, log: log).run(args)
173
164
  when 'push'
174
165
  require_relative '../lib/zold/commands/push'
175
166
  Zold::Push.new(wallets: wallets, remotes: remotes, log: log).run(args)
176
167
  when 'score'
177
168
  require_relative '../lib/zold/score'
178
- if args.length != 3
179
- raise 'Exactly three args required: host, port, strength'
180
- end
169
+ raise 'Exactly four args required: host, port, invoice, strength' if args.length != 4
181
170
  host = args[0]
182
- raise "Invalid host name: #{host}" unless host =~ /[a-z0-9\.-]+/
183
171
  port = args[1].to_i
184
- raise "Invalid TCP port: #{port}" if port <= 0 || port > 65535
185
- strength = args[2].to_i
172
+ invoice = args[2]
173
+ strength = args[3].to_i
186
174
  raise "Invalid strength: #{strength}" if strength <= 0 || strength > 8
187
- score = Zold::Score.new(
188
- Time.now, host, port, strength: strength
189
- )
175
+ score = Zold::Score.new(Time.now, host, port, invoice, strength: strength)
190
176
  loop do
191
177
  log.info(score.to_s)
192
178
  score = score.next
data/features/cli.feature CHANGED
@@ -12,6 +12,6 @@ Feature: Command Line Processing
12
12
  Then Exit code is zero
13
13
 
14
14
  Scenario: Wallet can be created
15
- When I run bin/zold with " --private-key id_rsa --public-key id_rsa.pub --trace create"
15
+ When I run bin/zold with "--trace create --public-key=id_rsa.pub"
16
16
  Then Exit code is zero
17
17
 
@@ -44,9 +44,7 @@ When(%r{^I run bin/zold with "([^"]*)"$}) do |arg|
44
44
  end
45
45
 
46
46
  Then(/^Stdout contains "([^"]*)"$/) do |txt|
47
- unless @stdout.include?(txt)
48
- raise "STDOUT doesn't contain '#{txt}':\n#{@stdout}"
49
- end
47
+ raise "STDOUT doesn't contain '#{txt}':\n#{@stdout}" unless @stdout.include?(txt)
50
48
  end
51
49
 
52
50
  Then(/^Stdout is empty$/) do
@@ -18,5 +18,7 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
19
  # SOFTWARE.
20
20
 
21
+ ENV['RACK_ENV'] = 'test'
22
+
21
23
  require 'simplecov'
22
24
  require_relative '../../lib/zold'
@@ -9,7 +9,9 @@ port=`python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.gets
9
9
 
10
10
  mkdir server
11
11
  cd server
12
- zold --trace node --host=localhost --port=${port} --bind-port=${port} --threads=0 --standalone &
12
+ zold --trace node --invoice=NOPREFIX@ffffffffffffffff \
13
+ --host=localhost --port=${port} --bind-port=${port} \
14
+ --threads=0 --standalone &
13
15
  pid=$!
14
16
  trap "kill -9 $pid" EXIT
15
17
  cd ..
@@ -23,10 +25,10 @@ zold --trace remote clean
23
25
  zold --trace remote add localhost ${port}
24
26
  zold --trace remote show
25
27
 
26
- zold --trace --public-key id_rsa.pub create 0000000000000000
27
- target=`zold --public-key id_rsa.pub create`
28
+ zold --trace create --public-key=id_rsa.pub 0000000000000000
29
+ target=`zold create --public-key=id_rsa.pub`
28
30
  invoice=`zold invoice ${target}`
29
- zold --trace --private-key id_rsa pay 0000000000000000 ${invoice} 14.99 'To save the world!'
31
+ zold --trace pay --private-key=id_rsa 0000000000000000 ${invoice} 14.99 'To save the world!'
30
32
  zold --trace propagate 0000000000000000
31
33
  zold --trace show
32
34
  zold --trace show 0000000000000000
data/lib/zold/amount.rb CHANGED
@@ -29,9 +29,9 @@ module Zold
29
29
  class Amount
30
30
  def initialize(coins: nil, zld: nil)
31
31
  raise 'You can\'t specify both coints and zld' if !coins.nil? && !zld.nil?
32
+ raise "Integer is required, while #{coins.class} provided: #{coins}" unless coins.nil? || coins.is_a?(Integer)
32
33
  @coins = coins unless coins.nil?
33
34
  @coins = (zld * 2**24).to_i unless zld.nil?
34
- raise "Integer is required: #{@coins.class}" unless @coins.is_a?(Integer)
35
35
  end
36
36
 
37
37
  ZERO = Amount.new(coins: 0)
@@ -54,21 +54,35 @@ module Zold
54
54
  end
55
55
 
56
56
  def ==(other)
57
+ raise '== may only work with Amount' unless other.is_a?(Amount)
57
58
  @coins == other.to_i
58
59
  end
59
60
 
60
61
  def >(other)
62
+ raise '> may only work with Amount' unless other.is_a?(Amount)
61
63
  @coins > other.to_i
62
64
  end
63
65
 
64
66
  def <(other)
67
+ raise '< may only work with Amount' unless other.is_a?(Amount)
65
68
  @coins < other.to_i
66
69
  end
67
70
 
71
+ def <=(other)
72
+ raise '<= may only work with Amount' unless other.is_a?(Amount)
73
+ @coins <= other.to_i
74
+ end
75
+
68
76
  def +(other)
77
+ raise '+ may only work with Amount' unless other.is_a?(Amount)
69
78
  Amount.new(coins: @coins + other.to_i)
70
79
  end
71
80
 
81
+ def -(other)
82
+ raise '- may only work with Amount' unless other.is_a?(Amount)
83
+ Amount.new(coins: @coins - other.to_i)
84
+ end
85
+
72
86
  def zero?
73
87
  @coins.zero?
74
88
  end
@@ -77,8 +91,8 @@ module Zold
77
91
  @coins < 0
78
92
  end
79
93
 
80
- def mul(m)
81
- c = @coins * m
94
+ def *(other)
95
+ c = (@coins * other).to_i
82
96
  raise "Overflow, can't multiply #{@coins} by #{m}" if c > 2**63
83
97
  Amount.new(coins: c)
84
98
  end
@@ -29,9 +29,8 @@ require_relative '../id'
29
29
  module Zold
30
30
  # Create command
31
31
  class Create
32
- def initialize(wallets:, pubkey:, log: Log::Quiet.new)
32
+ def initialize(wallets:, log: Log::Quiet.new)
33
33
  @wallets = wallets
34
- @pubkey = pubkey
35
34
  @log = log
36
35
  end
37
36
 
@@ -39,6 +38,10 @@ module Zold
39
38
  opts = Slop.parse(args, help: true) do |o|
40
39
  o.banner = "Usage: zold create [options]
41
40
  Available options:"
41
+ o.string '--public-key',
42
+ 'The location of RSA public key (default: ~/.ssh/id_rsa.pub)',
43
+ require: true,
44
+ default: '~/.ssh/id_rsa.pub'
42
45
  o.bool '--help', 'Print instructions'
43
46
  end
44
47
  if opts.help?
@@ -48,12 +51,12 @@ Available options:"
48
51
  create(opts.arguments.empty? ? Id.new : Id.new(opts.arguments[0]), opts)
49
52
  end
50
53
 
51
- def create(id, _)
54
+ def create(id, opts)
52
55
  wallet = @wallets.find(id)
53
- wallet.init(id, @pubkey)
56
+ key = Zold::Key.new(file: opts['public-key'])
57
+ wallet.init(id, key)
54
58
  @log.info(wallet.id)
55
- @log.debug("Wallet #{Rainbow(wallet).green} \
56
- created at #{@wallets.path}")
59
+ @log.debug("Wallet #{Rainbow(wallet).green} created at #{@wallets.path}")
57
60
  wallet
58
61
  end
59
62
  end
@@ -67,8 +67,7 @@ Available options:"
67
67
  @remotes.all.each do |r|
68
68
  total += 1 if fetch_one(id, r, cps, opts)
69
69
  end
70
- @log.debug("#{total} copies fetched, \
71
- there are #{cps.all.count} available locally")
70
+ @log.debug("#{total} copies fetched, there are #{cps.all.count} available locally")
72
71
  end
73
72
 
74
73
  def fetch_one(id, r, cps, opts)
@@ -80,34 +79,25 @@ there are #{cps.all.count} available locally")
80
79
  uri = URI("#{r[:home]}wallet/#{id}")
81
80
  res = Http.new(uri).get
82
81
  unless res.code == '200'
83
- @log.error(
84
- "#{address} #{Rainbow(res.code).red}/#{res.message} at #{uri}"
85
- )
82
+ @log.error("#{address} #{Rainbow(res.code).red}/#{res.message} at #{uri}")
86
83
  return false
87
84
  end
88
85
  json = JSON.parse(res.body)
89
- score = Score.new(
90
- Time.parse(json['score']['time']),
91
- r[:host],
92
- r[:port],
93
- json['score']['suffixes']
94
- )
86
+ score = Score.parse_json(json['score'])
95
87
  unless score.valid?
96
- @log.error("#{address} invalid score")
88
+ @log.error("#{address}: invalid score")
89
+ return false
90
+ end
91
+ if score.expired?
92
+ @log.error("#{address}: score expired")
97
93
  return false
98
94
  end
99
95
  if score.strength < Score::STRENGTH && !opts['ignore-score-weakness']
100
- @log.error(
101
- "#{address} score is too weak: #{score.strength} \
102
- (<#{Score::STRENGTH})"
103
- )
96
+ @log.error("#{address} score is too weak: #{score.strength} (<#{Score::STRENGTH})")
104
97
  return false
105
98
  end
106
- cps.add(json['body'], r[:host], r[:port], score.value)
107
- @log.info(
108
- "#{address} #{json['body'].length}b/\
109
- #{Rainbow(score.value).green} (v.#{json['version']})"
110
- )
99
+ cps.add(json['body'], score.host, score.port, score.value)
100
+ @log.info("#{address} #{json['body'].length}b/#{Rainbow(score.value).green} (v.#{json['version']})")
111
101
  true
112
102
  end
113
103
  end
@@ -40,6 +40,8 @@ module Zold
40
40
  def run(args = [])
41
41
  opts = Slop.parse(args, help: true) do |o|
42
42
  o.banner = 'Usage: zold node [options]'
43
+ o.string '--invoice',
44
+ 'The invoice you want to collect money to'
43
45
  o.integer '--port',
44
46
  "TCP port to open for the Net (default: #{Remotes::PORT})",
45
47
  default: Remotes::PORT
@@ -65,6 +67,7 @@ module Zold
65
67
  @log.info(opts.to_s)
66
68
  return
67
69
  end
70
+ raise '--invoice is mandatory' unless opts[:invoice]
68
71
  Zold::Front.set(:log, @log)
69
72
  Zold::Front.set(:logging, @log.debug?)
70
73
  FileUtils.mkdir_p(opts[:home])
@@ -75,18 +78,13 @@ module Zold
75
78
  AccessLog: []
76
79
  )
77
80
  if opts['standalone']
78
- Zold::Front.set(:remotes, Remotes::Empty.new)
81
+ remotes = Remotes::Empty.new
82
+ @log.debug('Running in standalone mode!')
79
83
  else
80
- Zold::Front.set(
81
- :remotes,
82
- Remotes.new(
83
- File.join(opts[:home], '.zoldata/remotes')
84
- )
85
- )
84
+ remotes = Remotes.new(File.join(opts[:home], 'zold-remotes'))
86
85
  end
87
86
  wallets = Wallets.new(File.join(opts[:home], 'zold-wallets'))
88
87
  Zold::Front.set(:wallets, wallets)
89
- remotes = Remotes.new(File.join(opts[:home], 'zold-remotes'))
90
88
  Zold::Front.set(:remotes, remotes)
91
89
  copies = File.join(opts[:home], 'zold-copies')
92
90
  Zold::Front.set(:copies, copies)
@@ -96,7 +94,7 @@ module Zold
96
94
  :entrance, Entrance.new(wallets, remotes, copies, address, log: @log)
97
95
  )
98
96
  Zold::Front.set(:port, opts['bind-port'])
99
- farm = Farm.new(log: @log)
97
+ farm = Farm.new(opts[:invoice], log: @log)
100
98
  farm.start(
101
99
  opts[:host], opts[:port],
102
100
  threads: opts[:threads], strength: opts[:strength]
@@ -28,9 +28,8 @@ require_relative '../log'
28
28
  module Zold
29
29
  # Money sending command
30
30
  class Pay
31
- def initialize(wallets:, pvtkey:, log: Log::Quiet.new)
31
+ def initialize(wallets:, log: Log::Quiet.new)
32
32
  @wallets = wallets
33
- @pvtkey = pvtkey
34
33
  @log = log
35
34
  end
36
35
 
@@ -43,6 +42,10 @@ Where:
43
42
  'amount' is the amount to pay, in ZLD, for example '14.95'
44
43
  'details' is the optional text to attach to the payment
45
44
  Available options:"
45
+ o.string '--private-key',
46
+ 'The location of RSA private key (default: ~/.ssh/id_rsa)',
47
+ require: true,
48
+ default: '~/.ssh/id_rsa'
46
49
  o.bool '--force',
47
50
  'Ignore all validations',
48
51
  default: false
@@ -68,12 +71,12 @@ Available options:"
68
71
  raise 'The amount can\'t be zero' if amount.zero?
69
72
  raise "The amount can't be negative: #{amount}" if amount.negative?
70
73
  if !from.root? && from.balance < @amount
71
- raise "There is not enough funds in #{from} to send #{amount}, \
72
- only #{payer.balance} left"
74
+ raise "There is not enough funds in #{from} to send #{amount}, only #{payer.balance} left"
73
75
  end
74
76
  end
75
- txn = from.sub(amount, invoice, @pvtkey, details)
76
- @log.debug("#{amount} sent from #{from} to #{invoice}: #{details}")
77
+ key = Zold::Key.new(file: opts['private-key'])
78
+ txn = from.sub(amount, invoice, key, details)
79
+ @log.debug("#{amount} sent from #{from} to #{txn.bnf}: #{details}")
77
80
  @log.info(txn.id)
78
81
  txn
79
82
  end