xrbp 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/examples/nodestore1.rb +3 -1
  3. data/lib/xrbp/common.rb +6 -0
  4. data/lib/xrbp/core_ext.rb +21 -0
  5. data/lib/xrbp/nodestore/format.rb +76 -26
  6. data/lib/xrbp/nodestore/ledger.rb +46 -14
  7. data/lib/xrbp/nodestore/parser.rb +47 -14
  8. data/lib/xrbp/nodestore/protocol/indexes.rb +11 -8
  9. data/lib/xrbp/nodestore/protocol/issue.rb +15 -0
  10. data/lib/xrbp/nodestore/protocol/rate.rb +13 -1
  11. data/lib/xrbp/nodestore/shamap/node_factory.rb +6 -1
  12. data/lib/xrbp/nodestore/shamap/node_id.rb +2 -2
  13. data/lib/xrbp/nodestore/sle/st_amount.rb +46 -181
  14. data/lib/xrbp/nodestore/sle/st_amount_arithmatic.rb +126 -0
  15. data/lib/xrbp/nodestore/sle/st_amount_comparison.rb +49 -0
  16. data/lib/xrbp/nodestore/sle/st_amount_conversion.rb +203 -0
  17. data/lib/xrbp/nodestore/sqldb.rb +69 -4
  18. data/lib/xrbp/overlay/handshake.rb +1 -1
  19. data/lib/xrbp/version.rb +1 -1
  20. data/spec/xrbp/crypto/account_spec.rb +7 -2
  21. data/spec/xrbp/nodestore/amendments_spec.rb +11 -0
  22. data/spec/xrbp/nodestore/db_parser.rb +64 -1
  23. data/spec/xrbp/nodestore/fees_spec.rb +3 -0
  24. data/spec/xrbp/nodestore/ledger_access.rb +87 -2
  25. data/spec/xrbp/nodestore/protocol/indexes_spec.rb +43 -0
  26. data/spec/xrbp/nodestore/protocol/rate_spec.rb +12 -0
  27. data/spec/xrbp/nodestore/shamap/inner_node_spec.rb +44 -0
  28. data/spec/xrbp/nodestore/shamap/node_factory_spec.rb +9 -0
  29. data/spec/xrbp/nodestore/shamap/node_id_spec.rb +6 -0
  30. data/spec/xrbp/nodestore/shamap/node_spec.rb +25 -0
  31. data/spec/xrbp/nodestore/shamap_spec.rb +144 -0
  32. data/spec/xrbp/nodestore/sle/st_amount_arithmatic_spec.rb +7 -0
  33. data/spec/xrbp/nodestore/sle/st_amount_comparison_spec.rb +11 -0
  34. data/spec/xrbp/nodestore/sle/st_amount_conversion_spec.rb +64 -0
  35. data/spec/xrbp/nodestore/sle/st_amount_spec.rb +47 -0
  36. data/spec/xrbp/nodestore/sle/st_ledger_entry_spec.rb +5 -0
  37. data/spec/xrbp/nodestore/sle/st_object_spec.rb +29 -0
  38. metadata +20 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cdd7b83c144366a77c0a68287e2ae511fa2878ef5b3be0e0f24b0b4d9e6a1be5
4
- data.tar.gz: 1db244c92cefaca23244dd7a20cbb89c0d9e2fc25ab10659483aaa8187f0f117
3
+ metadata.gz: 90ae30e5619f27c7a1f40b891a0e84b12f2de86bec369391d6e6f39f9a02bc89
4
+ data.tar.gz: 68365c498a34d0e4c7c3c805b59718f867fa46a6ab2784eab012b22726361212
5
5
  SHA512:
6
- metadata.gz: 42cdd0d6b998b3550a89233b90c86676fe9e56f7a4981cfad96f209fb3272ae2f669f5b16de399318596d13fb9c0fc2b087fa968155a4f30f211fed46cce74c4
7
- data.tar.gz: ee8e543e9e04557f8e8355dbd4f56a19f9176ba8e3f24c87db128298f38caa0386bf3763180bee734c94f7e91aaba3341c4689b4f7d4a53b3d4d0fa49d22b6d9
6
+ metadata.gz: e0660fab2c5f8248577df45b795d5d6794b03dbb33e88e566ec2e08f477147e74dc92d1703155dc152e103956ac586c38d6e1ad4b02fcd52594f9307f4d97c13
7
+ data.tar.gz: 3d49ec2789f104a194d6f5b6471cf40c3d0fc51695694157f2c79d189e21fe38ec3a94b2d8cbda9594a87f611f7b3440c5c3f0f9987d12a67782cf22f82c7d0e
@@ -21,4 +21,6 @@ puts nledger.order_book iou1, iou2
21
21
  puts nledger.txs
22
22
 
23
23
  require 'xrbp/nodestore/sqldb'
24
- puts XRBP::NodeStore::SQLDB.new("/var/lib/rippled/").ledger_hash_for_seq(49340234)
24
+ sql = XRBP::NodeStore::SQLDB.new("/var/lib/rippled/nudb")
25
+ puts sql.ledgers.hash_for_seq(49340234)
26
+ puts sql.ledgers.count
@@ -13,4 +13,10 @@ module XRBP
13
13
  return nil if xrp_time.nil?
14
14
  Time.at(xrp_time + 946684800)
15
15
  end
16
+
17
+ # Convert local time to XRP Time
18
+ def self.to_xrp_time(local_time)
19
+ return nil if local_time.nil?
20
+ local_time.to_i - 946684800
21
+ end
16
22
  end # module XRBP
@@ -59,6 +59,20 @@ class Integer
59
59
  end
60
60
  b
61
61
  end
62
+
63
+ def byte_string
64
+ bytes.reverse.pack("C*")
65
+ end
66
+
67
+ def to_int32
68
+ self & (2**32-1)
69
+ end
70
+
71
+ alias :to_int :to_int32
72
+
73
+ def from_xrp_time
74
+ XRBP.from_xrp_time(self)
75
+ end
62
76
  end
63
77
 
64
78
  # @private
@@ -71,3 +85,10 @@ class Array
71
85
  fill(x, length...n)
72
86
  end
73
87
  end
88
+
89
+ # @private
90
+ class Time
91
+ def to_xrp_time
92
+ XRBP.to_xrp_time(self)
93
+ end
94
+ end
@@ -383,32 +383,82 @@ module XRBP
383
383
  ###
384
384
 
385
385
  TX_TYPES = {
386
- -1 => :invalid,
387
- 0 => :payment,
388
- 1 => :escrow_create,
389
- 2 => :escrow_finish,
390
- 3 => :account_set,
391
- 4 => :escrow_cancel,
392
- 5 => :regular_key_set,
393
- 6 => :nickname_set, #open
394
- 7 => :offer_create,
395
- 8 => :offer_cancel,
396
-
397
- 9 => :no_longer_used,
398
-
399
- 11 => :ticket_create,
400
- 12 => :ticket_cancel,
401
- 13 => :signer_list_set,
402
- 14 => :paychan_create,
403
- 15 => :paychan_fund,
404
- 16 => :paychan_claim,
405
- 17 => :check_create,
406
- 18 => :check_cash,
407
- 19 => :check_cancel,
408
- 20 => :deposit_preauth,
409
-
410
- 100 => :amendment,
411
- 101 => :fee
386
+ -1 => :Invalid,
387
+ 0 => :Payment,
388
+ 1 => :EscrowCreate,
389
+ 2 => :EscrowFinish,
390
+ 3 => :AccountSet,
391
+ 4 => :EscrowCancel,
392
+ 5 => :SetRegularKey,
393
+ 6 => :NickNameSet, #open
394
+ 7 => :OfferCreate,
395
+ 8 => :OfferCancel,
396
+
397
+ 9 => :unused,
398
+
399
+ 11 => :TicketCreate,
400
+ 12 => :TicketCancel,
401
+ 13 => :SignerListSet,
402
+ 14 => :PaymentChannelCreate,
403
+ 15 => :PaymentChannelFund,
404
+ 16 => :PaymentChannelClaim,
405
+ 17 => :CheckCreate,
406
+ 18 => :CheckCash,
407
+ 19 => :CheckCancel,
408
+ 20 => :DepositPreauth,
409
+
410
+ 100 => :EnableAmendment,
411
+ 101 => :SetFee
412
+ }
413
+
414
+ # https://xrpl.org/transaction-results.html
415
+ TX_RESULTS = {
416
+ # tec
417
+ # https://xrpl.org/tec-codes.html
418
+ # "Transaction failed, but it was applied to a ledger to apply the transaction cost.
419
+ # They have numerical values in the range 100 to 199"
420
+ 100 => :tecCLAIM,
421
+ 146 => :tecCRYPTOCONDITION_ERROR,
422
+ 121 => :tecDIR_FULL,
423
+ 149 => :tecDUPLICATE,
424
+ 143 => :tecDST_TAG_NEEDED,
425
+ 148 => :tecEXPIRED,
426
+ 105 => :tecFAILED_PROCESSING,
427
+ 137 => :tecFROZEN,
428
+ 122 => :tecINSUF_RESERVE_LINE,
429
+ 123 => :tecINSUF_RESERVE_OFFER,
430
+ 141 => :tecINSUFFICIENT_RESERVE,
431
+ 144 => :tecINTERNAL,
432
+ 147 => :tecINVARIANT_FAILED,
433
+ 142 => :tecNEED_MASTER_KEY,
434
+ 130 => :tecNO_ALTERNATIVE_KEY,
435
+ 134 => :tecNO_AUTH,
436
+ 124 => :tecNO_DST,
437
+ 125 => :tecNO_DST_INSUF_XRP,
438
+ 140 => :tecNO_ENTRY,
439
+ 133 => :tecNO_ISSUER,
440
+ 150 => :tecKILLED,
441
+ 135 => :tecNO_LINE,
442
+ 126 => :tecNO_LINE_INSUF_RESERVE,
443
+ 127 => :tecNO_LINE_REDUNDANT,
444
+ 139 => :tecNO_PERMISSION,
445
+ 131 => :tecNO_REGULAR_KEY,
446
+ 138 => :tecNO_TARGET,
447
+ 145 => :tecOVERSIZE,
448
+ 132 => :tecOWNERS,
449
+ 128 => :tecPATH_DRY,
450
+ 101 => :tecPATH_PARTIAL,
451
+ 129 => :tecUNFUNDED,
452
+ 102 => :tecUNFUNDED_ADD,
453
+ 104 => :tecUNFUNDED_PAYMENT,
454
+ 103 => :tecUNFUNDED_OFFER,
455
+
456
+ # tef, tel, tem, ter transactions _not_
457
+ # applied to ledger
458
+
459
+ # tes
460
+ # https://xrpl.org/tes-success.html
461
+ 0 => :tesSUCCESS
412
462
  }
413
463
 
414
464
  ###
@@ -43,12 +43,16 @@ module XRBP
43
43
  @fees ||= Fees.new
44
44
  end
45
45
 
46
+ # Returns boolean indicating if specified account
47
+ # is flagged as globally frozen
46
48
  def global_frozen?(account)
47
49
  return false if account == Crypto.xrp_account
48
50
  sle = state_map.read(Indexes::account(account))
49
51
  return sle && sle.flag?(:global_freeze)
50
52
  end
51
53
 
54
+ # Returns boolean indicating if specific account
55
+ # has frozen trust-line for specified IOU
52
56
  def frozen?(account, iou)
53
57
  return false if iou[:currency] == 'XRP'
54
58
 
@@ -62,16 +66,19 @@ module XRBP
62
66
  :low_freeze)
63
67
  end
64
68
 
69
+ # Return IOU balance which owner account holds
65
70
  def account_holds(owner_id, iou)
66
71
  return xrp_liquid(owner_id, 0) if iou[:currency] == 'XRP'
67
72
  sle = state_map.read(Indexes::line(owner_id, iou))
68
73
  return STAmount.zero if !sle || frozen?(owner_id, iou)
69
74
 
70
75
  amount = sle.amount(:balance)
71
- amount.negate! if owner_id > iou[:account]
76
+ amount.negate! if Crypto.account_id(owner_id).to_bn >
77
+ Crypto.account_id(iou[:account]).to_bn
72
78
  balance_hook(amount)
73
79
  end
74
80
 
81
+ # Returns available (liquid) XRP account holds
75
82
  def xrp_liquid(account, owner_count_adj)
76
83
  sle = state_map.read(Indexes::account(account))
77
84
  return STAmount.zero unless sle
@@ -122,8 +129,12 @@ module XRBP
122
129
  count
123
130
  end
124
131
 
132
+ # Return TransferRate configured for IOU
133
+ #
134
+ # @see {Rate}
125
135
  def transfer_rate(issuer)
126
136
  sle = state_map.read(Indexes::account(issuer))
137
+
127
138
  return Rate.new sle.field(:uint32,
128
139
  :transfer_rate) if sle &&
129
140
  sle.field?(:transfer_rate)
@@ -134,6 +145,10 @@ module XRBP
134
145
 
135
146
  public
136
147
 
148
+ # TODO: helper method to get first funded high quailty
149
+ # offer from order book. Also market depth helper
150
+
151
+ # Return all offers for the given input/output currency pair
137
152
  def order_book(input, output)
138
153
  offers = []
139
154
 
@@ -145,6 +160,7 @@ module XRBP
145
160
  global_freeze = global_frozen?(output[:account]) ||
146
161
  global_frozen?(input[:account])
147
162
 
163
+ # transfer rate multipled to offer output to pay issuer
148
164
  rate = transfer_rate(output[:account])
149
165
 
150
166
  balances = {}
@@ -180,28 +196,32 @@ module XRBP
180
196
  # Read offer from db and process
181
197
  sle_offer = state_map.read(offer_index)
182
198
  if sle_offer
199
+ # Direct info from nodestore offer
183
200
  owner_id = sle_offer.account_id(:account)
184
201
  taker_gets = sle_offer.amount(:taker_gets)
185
202
  taker_pays = sle_offer.amount(:taker_pays)
186
203
 
187
- owner_funds = nil
188
- first_owner_offer = true
204
+ # Owner / Output Calculation
205
+ owner_funds = nil # how much of offer output the owner has
206
+ first_owner_offer = true # owner_funds returned w/ first owner offer
189
207
 
208
+ # issuer is offering it's own IOU, fully funded
190
209
  if output[:account] == owner_id
191
- # issuer is offering it's own IOU, fully funded
192
210
  owner_funds = taker_gets
193
211
 
212
+ # all offers not ours are unfunded
194
213
  elsif global_freeze
195
- # all offers not ours are unfunded
196
214
  owner_funds.clear(output)
197
215
 
198
216
  else
217
+ # if we have owner funds cached
199
218
  if balances[owner_id]
200
219
  owner_funds = balances[owner_id]
201
220
  first_owner_offer = false
202
221
 
222
+ # did not find balance in cache
203
223
  else
204
- # did not find balance in table
224
+ # lookup from nodestore
205
225
  owner_funds = account_holds(owner_id, output)
206
226
 
207
227
  # treat negative funds as zero
@@ -209,29 +229,33 @@ module XRBP
209
229
  end
210
230
  end
211
231
 
212
- offer = Hash[sle_offer.fields]
213
- taker_gets_funded = nil
214
- owner_funds_limit = owner_funds
215
- offer_rate = Rate.parity
232
+ offer = Hash[sle_offer.fields] # copy the offer fields to return
233
+ taker_gets_funded = nil # how much offer owner will actually be able to fund
234
+ owner_funds_limit = owner_funds # how much the offer owner has limited by the output transfer fee
235
+ offer_rate = Rate.parity # offer base output transfer rate
216
236
 
237
+ # Check if transfer fee applies,
217
238
  if rate != Rate.parity && # transfer fee
218
239
  # TODO: provide support for 'taker_id' rpc param:
219
240
  #taker_id != output[:account] && # not taking offers of own IOUs
220
241
  output[:account] != owner_id # offer owner not issuing own funds
221
242
  # Need to charge a transfer fee to offer owner.
222
243
  offer_rate = rate
223
- owner_funds_limit = owner_funds / offer_rate.rate
244
+ owner_funds_limit = owner_funds / offer_rate.to_amount
224
245
  end
225
246
 
226
-
247
+ # Check if owner has enough funds to pay it all
227
248
  if owner_funds_limit >= taker_gets
228
249
  # Sufficient funds no shenanigans.
229
250
  taker_gets_funded = taker_gets
230
251
 
231
252
  else
232
- # Only provide, if not fully funded.
253
+ # Only set these fields, if not fully funded.
233
254
  taker_gets_funded = owner_funds_limit
234
255
  offer[:taker_gets_funded] = taker_gets_funded
256
+
257
+ # the account that takes the offer will need to
258
+ # pay the 'gets' amount actually funded times the dir_rate (quality)
235
259
  offer[:taker_pays_funded] = [taker_pays,
236
260
  taker_gets_funded *
237
261
  dir_rate].min
@@ -240,13 +264,21 @@ module XRBP
240
264
  offer[:taker_pays_funded].issue = taker_pays.issue
241
265
  end
242
266
 
267
+ # Calculate how much owner will pay after this offer,
268
+ # if no transfer fee, then the amount funded,
269
+ # else the minimum of what the owner has or the
270
+ # amount funded w/ transfer fee
243
271
  owner_pays = (Rate.parity == offer_rate) ?
244
272
  taker_gets_funded :
245
273
  [owner_funds,
246
- taker_gets_funded * offer_rate].min
274
+ taker_gets_funded *
275
+ offer_rate.to_amount].min
247
276
 
277
+ # Update balance cache w/ new owner balance
248
278
  balances[owner_id] = owner_funds - owner_pays
249
279
 
280
+ # Set additional params and store the offer
281
+
250
282
  # include all offers funded and unfunded
251
283
  offer[:quality] = dir_rate
252
284
  offer[:owner_funds] = owner_funds if first_owner_offer
@@ -157,6 +157,9 @@ module XRBP
157
157
  elsif e == :transaction_type
158
158
  return Format::TX_TYPES[value]
159
159
 
160
+ elsif e == :transaction_result
161
+ return Format::TX_RESULTS[value]
162
+
160
163
  elsif e == :ledger_entry_type
161
164
  return Format::LEDGER_ENTRY_TYPE_CODES[value.chr]
162
165
  end
@@ -189,22 +192,39 @@ module XRBP
189
192
 
190
193
  # Parse 'Amount' data type from binary data.
191
194
  #
195
+ # Stored internally in 64 bits:
196
+ # - Bit 1 = 0 for XRP, 1 for IOU
197
+ # - Bit 2 = sign, 0 for negative, 1 for positive
198
+ #
199
+ # For XRP:
200
+ # - Remaining bits = value in drops
201
+ #
202
+ # For IOU:
203
+ # - Next 8 bits = Exponent
204
+ # - Next 54 bits = Mantissa
205
+ # Where value = mantissa * (10 ^ exponent)
206
+ # Note: 97 is added to exponent when serializing and subtracted when parsing so
207
+ # that effective nominal exponent is always in range of -96 to 80
208
+ #
192
209
  # @see https://developers.ripple.com/currency-formats.html
210
+ # @see STAmount(SerialIter& sit, SField const& name);
211
+ # @see {STAmount#from_wire}
193
212
  #
194
213
  # @protected
195
214
  def parse_amount(data)
196
215
  amount = data[0..7].unpack("Q>").first
197
- xrp = amount < 0x8000000000000000
198
-
199
- # FIXME : is sign/neg right (?)
200
216
 
201
- return STAmount.new(:issue => NodeStore.xrp_issue,
202
- :mantissa => amount & 0x3FFFFFFFFFFFFFFF), data[8..-1] if xrp
217
+ # native eg, xrp
218
+ is_xrp = (amount & STAmount::NOT_NATIVE) == 0
219
+ if is_xrp
220
+ is_pos = (amount & STAmount::POS_NATIVE) != 0
221
+ return STAmount.new(:issue => NodeStore.xrp_issue,
222
+ :mantissa => amount & ~STAmount::POS_NATIVE), data[8..-1] if is_pos
203
223
 
204
- sign = (amount & 0x4000000000000000) >> 62 # 0 = neg / 1 = pos
205
- neg = (sign == 0)
206
- exp = (amount & 0x3FC0000000000000) >> 54
207
- mant = (amount & 0x003FFFFFFFFFFFFF)
224
+ return STAmount.new(:issue => NodeStore.xrp_issue,
225
+ :mantissa => amount,
226
+ :neg => true), data[8..-1]
227
+ end
208
228
 
209
229
  data = data[8..-1]
210
230
  currency = Format::CURRENCY_CODE.decode(data)
@@ -213,12 +233,25 @@ module XRBP
213
233
  data = data[Format::CURRENCY_CODE.size..-1]
214
234
  issuer, data = parse_account(data, 20)
215
235
 
216
- sle = STAmount.new(:issue => Issue.new(currency, issuer),
217
- :neg => neg,
218
- :mantissa => mant,
219
- :exponent => exp)
236
+ issue = Issue.new(currency, issuer)
237
+
238
+ exp = (amount >> (64 - 10)).to_int
239
+ mant = (amount & ~(1023 << (64 - 10)))
240
+
241
+ if mant
242
+ neg = (exp & 256) == 0
243
+ exp = (exp & 255) - 97
244
+
245
+ sle = STAmount.new(:issue => issue,
246
+ :neg => neg,
247
+ :mantissa => mant,
248
+ :exponent => exp)
249
+
250
+ return sle, data
251
+ end
220
252
 
221
- return sle, data
253
+ raise unless exp == 512
254
+ return STAmount.new(:issue => issue)
222
255
  end
223
256
 
224
257
  # Parse 'Account' data type from binary data.
@@ -4,15 +4,14 @@ module XRBP
4
4
  module Indexes
5
5
 
6
6
  def self.get_quality(base)
7
- # FIXME: reverse to convert big to little endian,
7
+ # FIXME: assuming native platform is big endian,
8
8
  # need to account for all platforms
9
- base[-8..-1].reverse.to_bn
9
+ base[-8..-1].to_bn
10
10
  end
11
11
 
12
12
  def self.get_quality_next(base)
13
13
  nxt = "10000000000000000".to_i(16)
14
- (base.to_bn + nxt).bytes
15
- .reverse.pack("C*")
14
+ (base.to_bn + nxt).byte_string
16
15
  end
17
16
 
18
17
  ###
@@ -45,6 +44,14 @@ module XRBP
45
44
  sha512.digest[0..31]
46
45
  end
47
46
 
47
+ # TODO: Account Owner Dir from id
48
+ def self.owner_dir(id)
49
+ end
50
+
51
+ # TODO: Offer Index for account id and seq
52
+ def self.offer_index(id, seq)
53
+ end
54
+
48
55
  # Trust line for account/iou
49
56
  def self.line(account, iou)
50
57
  account = Crypto.account_id(account)
@@ -68,10 +75,6 @@ module XRBP
68
75
  sha512.digest[0..31]
69
76
  end
70
77
 
71
- # TODO: order book dir hash for ledger
72
- def self.order_book_dir()
73
- end
74
-
75
78
  # Order book index for given input/output
76
79
  def self.order_book(input, output)
77
80
  input = Hash[input]