class XRBP::NodeStore::Ledger
Attributes
db[R]
hash[R]
Public Class Methods
new(args={})
click to toggle source
# File lib/xrbp/nodestore/ledger.rb, line 12 def initialize(args={}) @db = args[:db] @hash = args[:hash] if @hash state_map.fetch_root [info["account_hash"]].pack("H*") tx_map.fetch_root [info["tx_hash"]].pack("H*") end end
Public Instance Methods
order_book(input, output)
click to toggle source
Return all offers for the given input/output currency pair
# File lib/xrbp/nodestore/ledger.rb, line 152 def order_book(input, output) offers = [] # Start at order book index # Stop after max order book quality tip_index = Indexes::order_book(input, output) book_end = Indexes::get_quality_next(tip_index) global_freeze = global_frozen?(output[:account]) || global_frozen?(input[:account]) # transfer rate multipled to offer output to pay issuer rate = transfer_rate(output[:account]) balances = {} done = false # set true when we cannot traverse anymore direct = true # set true when we need to find next dir offer_dir = nil # current directory being travred dir_rate = nil # current directory quality offer_index = nil # index of current offer being processed book_entry = nil # index of next offer directory record until done if direct direct = false # Return first index after tip ledger_index = state_map.succ(tip_index, book_end) if ledger_index # retrieve offer_dir SLE from db offer_dir = state_map.read(ledger_index) else offer_dir = nil end if !offer_dir done = true else # Set new tip, get first offer at new tip tip_index = offer_dir.key dir_rate = STAmount.from_quality(Indexes::get_quality(tip_index)) offer_index, offer_dir, book_entry = state_map.cdir_first(tip_index) end end if !done # Read offer from db and process sle_offer = state_map.read(offer_index) if sle_offer # Direct info from nodestore offer owner_id = sle_offer.account_id(:account) taker_gets = sle_offer.amount(:taker_gets) taker_pays = sle_offer.amount(:taker_pays) # Owner / Output Calculation owner_funds = nil # how much of offer output the owner has first_owner_offer = true # owner_funds returned w/ first owner offer # issuer is offering it's own IOU, fully funded if output[:account] == owner_id owner_funds = taker_gets # all offers not ours are unfunded elsif global_freeze owner_funds.clear(output) else # if we have owner funds cached if balances[owner_id] owner_funds = balances[owner_id] first_owner_offer = false # did not find balance in cache else # lookup from nodestore owner_funds = account_holds(owner_id, output) # treat negative funds as zero owner_funds.clear if owner_funds < STAmount.zero end end offer = Hash[sle_offer.fields] # copy the offer fields to return taker_gets_funded = nil # how much offer owner will actually be able to fund owner_funds_limit = owner_funds # how much the offer owner has limited by the output transfer fee offer_rate = Rate.parity # offer base output transfer rate # Check if transfer fee applies, if rate != Rate.parity && # transfer fee # TODO: provide support for 'taker_id' rpc param: #taker_id != output[:account] && # not taking offers of own IOUs output[:account] != owner_id # offer owner not issuing own funds # Need to charge a transfer fee to offer owner. offer_rate = rate owner_funds_limit = owner_funds / offer_rate.to_amount end # Check if owner has enough funds to pay it all if owner_funds_limit >= taker_gets # Sufficient funds no shenanigans. taker_gets_funded = taker_gets else # Only set these fields, if not fully funded. taker_gets_funded = owner_funds_limit offer[:taker_gets_funded] = taker_gets_funded # the account that takes the offer will need to # pay the 'gets' amount actually funded times the dir_rate (quality) offer[:taker_pays_funded] = [taker_pays, taker_gets_funded * dir_rate].min # XXX: done in multiply operation in rippled offer[:taker_pays_funded].issue = taker_pays.issue end # Calculate how much owner will pay after this offer, # if no transfer fee, then the amount funded, # else the minimum of what the owner has or the # amount funded w/ transfer fee owner_pays = (Rate.parity == offer_rate) ? taker_gets_funded : [owner_funds, taker_gets_funded * offer_rate.to_amount].min # Update balance cache w/ new owner balance balances[owner_id] = owner_funds - owner_pays # Set additional params and store the offer # include all offers funded and unfunded offer[:quality] = dir_rate offer[:owner_funds] = owner_funds if first_owner_offer offers << offer else puts "missing offer" end # Retrieve next offer in offer_dir, # updating offer_index, offer_dir, book_entry appropriately offer_index, offer_dir, book_entry = *state_map.cdir_next(tip_index, offer_dir, book_entry) # if next offer not retrieved find next record after tip direct = true if !offer_index end end return offers end
txs()
click to toggle source
# File lib/xrbp/nodestore/ledger.rb, line 22 def txs @txs ||= tx_map.collect { |tx| parse_tx_inner(tx.data) } end
Private Instance Methods
account_holds(owner_id, iou)
click to toggle source
Return IOU balance which owner account holds
# File lib/xrbp/nodestore/ledger.rb, line 70 def account_holds(owner_id, iou) return xrp_liquid(owner_id, 0) if iou[:currency] == 'XRP' sle = state_map.read(Indexes::line(owner_id, iou)) return STAmount.zero if !sle || frozen?(owner_id, iou) amount = sle.amount(:balance) amount.negate! if Crypto.account_id(owner_id).to_bn > Crypto.account_id(iou[:account]).to_bn balance_hook(amount) end
balance_hook(amount)
click to toggle source
# File lib/xrbp/nodestore/ledger.rb, line 121 def balance_hook(amount) # TODO currently implementing ReadView::balanceHook, # implement PaymentSandbox::balanceHook? amount end
confine_owner_account(current, adjustment)
click to toggle source
# File lib/xrbp/nodestore/ledger.rb, line 109 def confine_owner_account(current, adjustment) adjusted = current + adjustment if adjustment > 0 # XXX: std::numeric_limits<std::uint32_t>::max adjusted = 2**32-1 if adjusted < current else adjusted = 0 if adjusted > current end adjusted end
fees()
click to toggle source
# File lib/xrbp/nodestore/ledger.rb, line 42 def fees @fees ||= Fees.new end
frozen?(account, iou)
click to toggle source
Returns boolean indicating if specific account has frozen trust-line for specified IOU
# File lib/xrbp/nodestore/ledger.rb, line 56 def frozen?(account, iou) return false if iou[:currency] == 'XRP' sle = state_map.read(Indexes::account(iou[:account])) return true if sle && sle.flag?(:global_freeze) return false if iou[:account] == account sle = state_map.read(Indexes::line(account, iou)) sle && sle.flag?(iou[:account] > account ? :high_freeze : :low_freeze) end
global_frozen?(account)
click to toggle source
Returns boolean indicating if specified account is flagged as globally frozen
# File lib/xrbp/nodestore/ledger.rb, line 48 def global_frozen?(account) return false if account == Crypto.xrp_account sle = state_map.read(Indexes::account(account)) return sle && sle.flag?(:global_freeze) end
info()
click to toggle source
# File lib/xrbp/nodestore/ledger.rb, line 38 def info @info ||= db.ledger(hash) end
owner_count_hook(count)
click to toggle source
# File lib/xrbp/nodestore/ledger.rb, line 127 def owner_count_hook(count) # Same PaymentSandbox TODO as in balance_hook above count end
state_map()
click to toggle source
# File lib/xrbp/nodestore/ledger.rb, line 30 def state_map @state_map ||= SHAMap.new :db => db end
transfer_rate(issuer)
click to toggle source
Return TransferRate configured for IOU
@see {Rate}
# File lib/xrbp/nodestore/ledger.rb, line 135 def transfer_rate(issuer) sle = state_map.read(Indexes::account(issuer)) return Rate.new sle.field(:uint32, :transfer_rate) if sle && sle.field?(:transfer_rate) Rate.parity end
tx_map()
click to toggle source
# File lib/xrbp/nodestore/ledger.rb, line 34 def tx_map @tx_map ||= SHAMap.new :db => db end
xrp_liquid(account, owner_count_adj)
click to toggle source
Returns available (liquid) XRP account holds
# File lib/xrbp/nodestore/ledger.rb, line 82 def xrp_liquid(account, owner_count_adj) sle = state_map.read(Indexes::account(account)) return STAmount.zero unless sle if fix1141? info['parent_close_time'] owner_count = confine_owner_account(owner_count_hook( sle.field(:uint32, :owner_count)), owner_count_adj) reserve = fees.account_reserve(owner_count) full_balance = sle.amount(:balance) balance = balance_hook(full_balance) amount = balance - reserve return STAmount.zero if balance < reserve return amount else owner_count = confine_owner_account(sle.field(:uint32, :owner_count), owner_count_adj) reserve = fees.account_reserve(sle.field(:uint32, :owner_count)) full_balance = sle.amount(:balance) amount = balance - reserve return STAmount.zero if balance < reserve return balance_hook(amount) end end