class Zold::Taxes

Taxes command.

The user pays taxes for his/her wallet by running 'zold taxes pay'. As the White Paper explains (find it at papers.zold.io), each wallet has to pay certain amount of taxes in order to be accepted by any node in the network. Of course, a node may make a decision to accept and store any wallet, even if taxes are not paid, but the majority of nodes will obey the rules and will reject wallets that haven't paid enough taxes.

Taxes are paid from wallet to wallet, not from clients to nodes. A wallet just selects the most suitable wallet to transfer taxes to and sends the payment. More details you can find in the White Paper.

Public Class Methods

new(wallets:, remotes:, log: Log::NULL) click to toggle source
# File lib/zold/commands/taxes.rb, line 57
def initialize(wallets:, remotes:, log: Log::NULL)
  @wallets = wallets
  @remotes = remotes
  @log = log
end

Public Instance Methods

run(args = []) click to toggle source
# File lib/zold/commands/taxes.rb, line 63
    def run(args = [])
      opts = Slop.parse(args, help: true, suppress_errors: true) do |o|
        o.banner = "Usage: zold taxes command [options]
Available commands:
    #{Rainbow('taxes pay').green} wallet
      Pay taxes for the given wallet
    #{Rainbow('taxes show').green}
      Show taxes status for the given wallet
    #{Rainbow('taxes debt').green}
      Show current debt
Available options:"
        o.string '--private-key',
          'The location of RSA private key (default: ~/.ssh/id_rsa)',
          require: true,
          default: '~/.ssh/id_rsa'
        o.bool '--ignore-score-weakness',
          'Don\'t complain when their score is too weak',
          default: false
        o.string '--keygap',
          'Keygap, if the private RSA key is not complete',
          default: ''
        o.bool '--ignore-nodes-absence',
          'Don\'t complain if there are not enough nodes in the network to pay taxes',
          default: false
        o.bool '--help', 'Print instructions'
      end
      mine = Args.new(opts, @log).take || return
      command = mine[0]
      case command
      when 'show'
        raise 'At least one wallet ID is required' unless mine[1]
        mine[1..-1].each do |id|
          @wallets.acq(Id.new(id)) do |w|
            show(w, opts)
          end
        end
      when 'debt'
        raise 'At least one wallet ID is required' unless mine[1]
        mine[1..-1].each do |id|
          @wallets.acq(Id.new(id)) do |w|
            debt(w, opts)
          end
        end
      when 'pay'
        raise 'At least one wallet ID is required' unless mine[1]
        mine[1..-1].each do |id|
          @wallets.acq(Id.new(id), exclusive: true) do |w|
            pay(w, opts)
          end
        end
      else
        @log.info(opts.to_s)
      end
    end

Private Instance Methods

debt(wallet, _) click to toggle source
# File lib/zold/commands/taxes.rb, line 165
def debt(wallet, _)
  raise 'The wallet is absent' unless wallet.exists?
  tax = Tax.new(wallet)
  @log.info(tax.debt)
  @log.debug(tax.to_text)
  @log.debug('Read the White Paper for more details: https://papers.zold.io/wp.pdf')
end
pay(wallet, opts) click to toggle source
# File lib/zold/commands/taxes.rb, line 120
    def pay(wallet, opts)
      raise 'The wallet is absent' unless wallet.exists?
      tax = Tax.new(wallet)
      debt = total = tax.debt
      @log.info("The current debt of #{wallet.mnemo} is #{debt} (#{debt.to_i} zents), \
the balance is #{wallet.balance}: #{tax.to_text}")
      unless tax.in_debt?
        @log.debug("No need to pay taxes yet, while the debt is less than #{Tax::TRIAL} (#{Tax::TRIAL.to_i} zents)")
        return
      end
      top = top_scores(opts)
      everybody = top.dup
      paid = 0
      while debt > Tax::TRIAL
        if top.empty?
          msg = [
            "There were #{everybody.count} remote nodes as tax collecting candidates;",
            "#{paid} payments have been made;",
            "there was not enough score power to pay the total debt of #{total} for #{wallet.id};",
            "the residual amount to pay is #{debt} (trial amount is #{Tax::TRIAL});",
            "the formula ingredients are #{tax.to_text}"
          ].join(' ')
          raise msg unless opts['ignore-nodes-absence']
          @log.info(msg)
          break
        end
        best = top.shift
        if tax.exists?(tax.details(best))
          @log.debug("The score has already been taxed: #{best}")
          next
        end
        pem = IO.read(opts['private-key'])
        unless opts['keygap'].empty?
          pem = pem.sub('*' * opts['keygap'].length, opts['keygap'])
          @log.debug("Keygap \"#{'*' * opts['keygap'].length}\" injected into the RSA private key")
        end
        txn = tax.pay(Zold::Key.new(text: pem), best)
        debt += txn.amount
        paid += 1
        @log.info("#{txn.amount * -1} of taxes paid from #{wallet.id} to #{txn.bnf} \
(payment no.#{paid}, txn ##{txn.id}/#{wallet.txns.count}), #{debt} left to pay")
      end
      @log.info('The wallet is in good standing, all taxes paid') unless tax.in_debt?
    end
show(wallet, _) click to toggle source
# File lib/zold/commands/taxes.rb, line 173
def show(wallet, _)
  raise 'The wallet is absent' unless wallet.exists?
  tax = Tax.new(wallet)
  @log.info(tax.to_text)
  @log.info('Read the White Paper for more details: https://papers.zold.io/wp.pdf')
end
top_scores(opts) click to toggle source
# File lib/zold/commands/taxes.rb, line 180
def top_scores(opts)
  best = []
  @remotes.iterate(@log) do |r|
    uri = '/'
    res = r.http(uri).get
    r.assert_code(200, res)
    json = JsonPage.new(res.body, uri).to_hash
    score = Score.parse_json(json['score'])
    r.assert_valid_score(score)
    r.assert_score_strength(score) unless opts['ignore-score-weakness']
    r.assert_score_value(score, Tax::EXACT_SCORE)
    @log.info("#{r}: #{Rainbow(score.value).green} to #{score.invoice}")
    best << score
  end
  best.sort_by(&:value).reverse
end