xrbp 0.2.1 → 0.2.2

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/examples/nodestore1.rb +12 -7
  3. data/lib/xrbp/core_ext.rb +27 -0
  4. data/lib/xrbp/crypto/account.rb +28 -3
  5. data/lib/xrbp/nodestore.rb +6 -0
  6. data/lib/xrbp/nodestore/amendments.rb +13 -0
  7. data/lib/xrbp/nodestore/backends/decompressor.rb +8 -6
  8. data/lib/xrbp/nodestore/backends/nudb.rb +2 -0
  9. data/lib/xrbp/nodestore/backends/rocksdb.rb +1 -0
  10. data/lib/xrbp/nodestore/db.rb +5 -387
  11. data/lib/xrbp/nodestore/fees.rb +19 -0
  12. data/lib/xrbp/nodestore/format.rb +72 -1
  13. data/lib/xrbp/nodestore/ledger.rb +272 -0
  14. data/lib/xrbp/nodestore/parser.rb +407 -0
  15. data/lib/xrbp/nodestore/protocol.rb +5 -0
  16. data/lib/xrbp/nodestore/protocol/currency.rb +11 -0
  17. data/lib/xrbp/nodestore/protocol/indexes.rb +109 -0
  18. data/lib/xrbp/nodestore/protocol/issue.rb +26 -0
  19. data/lib/xrbp/nodestore/protocol/quality.rb +10 -0
  20. data/lib/xrbp/nodestore/protocol/rate.rb +21 -0
  21. data/lib/xrbp/nodestore/shamap.rb +447 -0
  22. data/lib/xrbp/nodestore/shamap/errors.rb +8 -0
  23. data/lib/xrbp/nodestore/shamap/inner_node.rb +98 -0
  24. data/lib/xrbp/nodestore/shamap/item.rb +14 -0
  25. data/lib/xrbp/nodestore/shamap/node.rb +49 -0
  26. data/lib/xrbp/nodestore/shamap/node_factory.rb +120 -0
  27. data/lib/xrbp/nodestore/shamap/node_id.rb +83 -0
  28. data/lib/xrbp/nodestore/shamap/tagged_cache.rb +20 -0
  29. data/lib/xrbp/nodestore/shamap/tree_node.rb +21 -0
  30. data/lib/xrbp/nodestore/sle.rb +4 -0
  31. data/lib/xrbp/nodestore/sle/st_account.rb +8 -0
  32. data/lib/xrbp/nodestore/sle/st_amount.rb +226 -0
  33. data/lib/xrbp/nodestore/sle/st_ledger_entry.rb +24 -0
  34. data/lib/xrbp/nodestore/sle/st_object.rb +46 -0
  35. data/lib/xrbp/nodestore/sqldb.rb +23 -0
  36. data/lib/xrbp/nodestore/uint.rb +7 -0
  37. data/lib/xrbp/version.rb +1 -1
  38. data/spec/xrbp/nodestore/backends/nudb_spec.rb +3 -1
  39. data/spec/xrbp/nodestore/backends/rocksdb_spec.rb +1 -1
  40. data/spec/xrbp/nodestore/{backends/db_parser.rb → db_parser.rb} +2 -2
  41. data/spec/xrbp/nodestore/ledger_access.rb +17 -0
  42. metadata +30 -3
@@ -0,0 +1,5 @@
1
+ require_relative './protocol/currency'
2
+ require_relative './protocol/issue'
3
+ require_relative './protocol/indexes'
4
+ require_relative './protocol/rate'
5
+ require_relative './protocol/quality'
@@ -0,0 +1,11 @@
1
+ module XRBP
2
+ module NodeStore
3
+ def self.xrp_currency
4
+ @xrp_currency ||= 0
5
+ end
6
+
7
+ def self.no_currency
8
+ @no_currency ||= 1
9
+ end
10
+ end # module NodeStore
11
+ end # module XRBP
@@ -0,0 +1,109 @@
1
+ module XRBP
2
+ module NodeStore
3
+ # Return DB lookup indices for the following artifacts
4
+ module Indexes
5
+
6
+ def self.get_quality(base)
7
+ # FIXME: reverse to convert big to little endian,
8
+ # need to account for all platforms
9
+ base[-8..-1].reverse.to_bn
10
+ end
11
+
12
+ def self.get_quality_next(base)
13
+ nxt = "10000000000000000".to_i(16)
14
+ (base.to_bn + nxt).bytes
15
+ .reverse.pack("C*")
16
+ end
17
+
18
+ ###
19
+
20
+ def self.dir_node_index(root, index)
21
+ return root if index == 0
22
+
23
+ sha512 = OpenSSL::Digest::SHA512.new
24
+ sha512 << "\0"
25
+ sha512 << Format::LEDGER_NAMESPACE[:dir_node]
26
+ sha512 << root
27
+ sha512 << index.bytes.rjust!(8, 0).pack("C*")
28
+
29
+ sha512.digest[0..31]
30
+ end
31
+
32
+ def self.page(key, index)
33
+ dir_node_index key, index
34
+ end
35
+
36
+ # Account index from id
37
+ def self.account(id)
38
+ id = Crypto.account_id(id)
39
+
40
+ sha512 = OpenSSL::Digest::SHA512.new
41
+ sha512 << "\0"
42
+ sha512 << Format::LEDGER_NAMESPACE[:account]
43
+ sha512 << id
44
+
45
+ sha512.digest[0..31]
46
+ end
47
+
48
+ # Trust line for account/iou
49
+ def self.line(account, iou)
50
+ account = Crypto.account_id(account)
51
+ issuer = Crypto.account_id(iou[:account])
52
+
53
+ sha512 = OpenSSL::Digest::SHA512.new
54
+ sha512 << "\0"
55
+ sha512 << Format::LEDGER_NAMESPACE[:ripple]
56
+
57
+ if account.to_bn < issuer.to_bn
58
+ sha512 << account
59
+ sha512 << issuer
60
+
61
+ else
62
+ sha512 << issuer
63
+ sha512 << account
64
+ end
65
+
66
+ sha512 << Format.encode_currency(iou[:currency])
67
+
68
+ sha512.digest[0..31]
69
+ end
70
+
71
+ # TODO: order book dir hash for ledger
72
+ def self.order_book_dir()
73
+ end
74
+
75
+ # Order book index for given input/output
76
+ def self.order_book(input, output)
77
+ input = Hash[input]
78
+ output = Hash[output]
79
+
80
+ # Currency always upcase
81
+ input[:currency].upcase!
82
+ output[:currency].upcase!
83
+
84
+ # If currency == 'XRP' set corresponding issuer
85
+ input[:account] = Crypto.xrp_account if input[:currency] == 'XRP'
86
+ output[:account] = Crypto.xrp_account if output[:currency] == 'XRP'
87
+
88
+ # Convert currency to binary representation
89
+ input[:currency] = Format.encode_currency(input[:currency])
90
+ output[:currency] = Format.encode_currency(output[:currency])
91
+
92
+ # convert input / output account to binary representation
93
+ input[:account] = Crypto.account_id(input[:account])
94
+ output[:account] = Crypto.account_id(output[:account])
95
+
96
+ book_base = ["\0", Format::LEDGER_NAMESPACE[:book_dir],
97
+ input[:currency], output[:currency],
98
+ input[:account], output[:account]].join
99
+
100
+ sha512 = OpenSSL::Digest::SHA512.new
101
+ book_base = sha512.digest(book_base)[0..31]
102
+
103
+ # XXX: get_quality_index shorthand:
104
+ book_base[-8..-1] = [0, 0, 0, 0, 0, 0, 0, 0].pack("C*")
105
+ book_base
106
+ end
107
+ end # module Indexes
108
+ end # module NodeStore
109
+ end # module XRBP
@@ -0,0 +1,26 @@
1
+ module XRBP
2
+ module NodeStore
3
+ class Issue
4
+ attr_reader :currency, :account
5
+
6
+ def initialize(currency, account)
7
+ @currency = currency
8
+ @account = account
9
+ end
10
+
11
+ def xrp?
12
+ self == NodeStore.xrp_issue
13
+ end
14
+ end # class Issue
15
+
16
+ def self.xrp_issue
17
+ @xrp_issue ||= Issue.new(NodeStore.xrp_currency,
18
+ Crypto.xrp_account)
19
+ end
20
+
21
+ def self.no_issue
22
+ @no_issue ||= Issue.new(NodeStore.no_currency,
23
+ Crypto.no_account)
24
+ end
25
+ end # module NodeStore
26
+ end # module XRBP
@@ -0,0 +1,10 @@
1
+ module XRBP
2
+ module NodeStore
3
+ # Ripple specific constant used for parsing qualities and other things
4
+ # https://github.com/ripple/rippled/blob/develop/src/ripple/protocol/Quality.h#L107
5
+ QUALITY_ONE = 1000000000
6
+
7
+ class Quality
8
+ end # class Quality
9
+ end # module NodeStore
10
+ end # module XRBP
@@ -0,0 +1,21 @@
1
+ module XRBP
2
+ module NodeStore
3
+ class Rate
4
+ attr_reader :rate
5
+
6
+ def initialize(rate=nil)
7
+ @rate = rate
8
+ end
9
+
10
+ def self.parity
11
+ @parity ||= Rate.new(QUALITY_ONE)
12
+ end
13
+
14
+ def to_amount
15
+ STAmount.new :issue => Issue.no_issue,
16
+ :mantissa => rate,
17
+ :exponent => -9
18
+ end
19
+ end # class Rate
20
+ end # module NodeStore
21
+ end # module XRBP
@@ -0,0 +1,447 @@
1
+ require_relative './shamap/errors'
2
+ require_relative './shamap/node_id'
3
+ require_relative './shamap/node'
4
+ require_relative './shamap/inner_node'
5
+ require_relative './shamap/tree_node'
6
+ require_relative './shamap/item'
7
+ require_relative './shamap/tagged_cache'
8
+
9
+ module XRBP
10
+ class SHAMap
11
+ include Enumerable
12
+
13
+ def initialize(args={})
14
+ @db = args[:db]
15
+ @version = args[:version]
16
+
17
+ if @version == 2
18
+ @root = InnerNode.new :v2 => true,
19
+ :depth => 0
20
+ else
21
+ @root = InnerNode.new :v2 => false
22
+ end
23
+ end
24
+
25
+ # Invoke callback block w/ each sequential SHAMap item
26
+ # Implements Enumerable interface.
27
+ def each
28
+ current, stack = *peek_first_item
29
+ until current.nil?
30
+ yield current.item
31
+ current, stack = *peek_next_item(current.item.key, stack)
32
+ end
33
+
34
+ return self
35
+ end
36
+
37
+ # Return the next key in tree greater than
38
+ # specified one and less than last
39
+ def succ(key, last)
40
+ item = upper_bound(key)
41
+ return nil if item == map_end
42
+ return nil if last && item.key.to_bn >= last.to_bn
43
+ return item.key
44
+ end
45
+
46
+ # Read Key from database and return
47
+ # corresponding SLE
48
+ def read(key)
49
+ raise if key.zero?
50
+ item = peek_item(key)
51
+ return nil unless item
52
+ sle = NodeStore::SLE.new :item => item,
53
+ :key => key
54
+ #return nil unless key.check?(sle)
55
+ sle
56
+ end
57
+
58
+ ###
59
+
60
+ # Return node corresponding to key
61
+ # or nil if not found
62
+ def peek_item(key)
63
+ leaf = find_key(key)
64
+ return nil unless leaf
65
+ leaf.peek_item
66
+ end
67
+
68
+ # Return node corresponding to first item in map
69
+ def peek_first_item
70
+ stack = []
71
+ node, stack = *first_below(@root, stack)
72
+ return nil unless node
73
+ return node, stack
74
+ end
75
+
76
+ # Return node corresponding to next sequential
77
+ # item in map
78
+ def peek_next_item(id, stack)
79
+ raise if stack.empty?
80
+ raise unless stack.last.first.leaf?
81
+ stack.pop
82
+
83
+ until stack.empty?
84
+ node, node_id = *stack.last
85
+ raise if node.leaf?
86
+
87
+ # Select next higher tree branch
88
+ inner = node
89
+ (node_id.select_branch(id) + 1).upto(15) { |b|
90
+ next if inner.empty_branch?(b)
91
+ node = descend_throw(inner, b)
92
+ leaf, stack = *first_below(node, stack, b)
93
+ raise unless leaf && leaf.leaf?
94
+ return leaf, stack
95
+ }
96
+
97
+ stack.pop
98
+ end
99
+
100
+ return nil
101
+ end
102
+
103
+ # Fetch node from database raising
104
+ # error if it is not found
105
+ def fetch_node(key)
106
+ node = fetch_node_nt(key)
107
+ raise unless node
108
+ node
109
+ end
110
+
111
+ # Retrieve node from db.
112
+ # nt = no throw
113
+ def fetch_node_nt(key)
114
+ res = get_cache(key)
115
+ return res if res
116
+ canonicalize(key, fetch_node_from_db(key))
117
+ end
118
+
119
+ # Fetch key from database and assign
120
+ # result as root element
121
+ def fetch_root(key)
122
+ return true if key == root.hash
123
+
124
+ root = fetch_node_nt(key)
125
+ return false unless root
126
+
127
+ @root = root
128
+ return true
129
+ end
130
+
131
+ private
132
+
133
+ attr_reader :db, :root
134
+
135
+ def v2?
136
+ root && root.v2?
137
+ end
138
+
139
+ def map_end
140
+ :end
141
+ end
142
+
143
+ # Used to cache nodes by key
144
+ def treecache
145
+ @treecache ||= TaggedCache.new
146
+ end
147
+
148
+ ###
149
+
150
+ # Return node in cache corresponding to key
151
+ def get_cache(key)
152
+ treecache.fetch(key)
153
+ end
154
+
155
+ # Return node in tree corresponding to key, else nil
156
+ def find_key(key)
157
+ leaf, stack = walk_towards_key(key)
158
+ return nil if leaf && leaf.peek_item.key != key
159
+ leaf
160
+ end
161
+
162
+ # Retreive specified key from database and
163
+ # create new Node-subclass instance corresponding
164
+ # to record type.
165
+ def fetch_node_from_db(key)
166
+ # XXX: shorthand object decoding by removing unused & type fields
167
+ obj = db[key][9..-1]
168
+
169
+ begin
170
+ node = Node.make(obj, 0, :prefix, key, true)
171
+
172
+ if node && node.inner?
173
+ if node.v2? != v2?
174
+ raise unless root && root.empty?
175
+ if v2?
176
+ @root = make_v2
177
+ else
178
+ @root = make_v1
179
+ end
180
+ end
181
+ end
182
+
183
+ return node
184
+ rescue Exception
185
+ puts "TODO: verify"
186
+ return TreeNode.new
187
+ end
188
+ end
189
+
190
+ # Canonicalize/cache key/node in treecache
191
+ def canonicalize(key, node)
192
+ treecache.canonicalize(key, node)
193
+ end
194
+
195
+ # Return bool indicating if node is
196
+ # inconsistent with this tree
197
+ def inconsistent_node?(node)
198
+ return true if !root ||
199
+ !node
200
+ return false if node.tree_node?
201
+ v2 = node.v2?
202
+ return true unless !v2 || node.depth != 0
203
+ return false if v2 == v2?
204
+
205
+ #state = INVALID
206
+ return true
207
+ end
208
+
209
+ ###
210
+
211
+ # Return first item in tree _after_ given
212
+ # key (eg whose key is > given key).
213
+ #
214
+ # Given item does not need to be in tree.
215
+ def upper_bound(key)
216
+ # Return traversal stack to key
217
+ leaf, stack = walk_towards_key(key)
218
+
219
+ # Pop the stack until empty
220
+ until stack.empty?
221
+ node, node_id = *stack.last
222
+
223
+ # If current item is leaf, return if
224
+ # item.key > key
225
+ if node.leaf?
226
+ if node.item.key.to_bn > key.to_bn
227
+ return node.item
228
+ end
229
+
230
+ # If inner node, select next higher
231
+ # branch to traverse
232
+ else
233
+ branch = nil
234
+ if v2?
235
+ if node.common_prefix?(key)
236
+ branch = node_id.select_branch(key) + 1
237
+ elsif key.to_bn < node.common.to_bn
238
+ branch = 0
239
+ else
240
+ branch = 16
241
+ end
242
+ else
243
+ branch = node_id.select_branch(key) + 1
244
+ end
245
+
246
+ # Start traversal from selected branch
247
+ # on up, returning first node below
248
+ # non-empty branches
249
+ inner = node
250
+ branch.upto(15) { |b|
251
+ next if inner.empty_branch?(b)
252
+ node = descend_throw inner, b
253
+ leaf, stack = first_below node, stack, b
254
+ raise Error::MissingNode unless leaf
255
+ return leaf.item
256
+ }
257
+ end
258
+
259
+ stack.pop
260
+ end
261
+
262
+ # If no items > this one, return map_end
263
+ map_end
264
+ end
265
+
266
+ # Descends Inner Tree Nodes in NodeStore
267
+ # until we reach non-inner-node.
268
+ #
269
+ # Return complete stack of walk.
270
+ def walk_towards_key(key)
271
+ stack = []
272
+
273
+ # Start with root node
274
+ in_node = root
275
+ node_id = NodeID.new
276
+
277
+ # Iterate until node is no longer inner
278
+ while in_node.inner?
279
+ stack.push [in_node, node_id]
280
+
281
+ return nil, stack if v2? && in_node.common_prefix?(key)
282
+
283
+ # Select tree branch which has key
284
+ # we are looking for, ensure it is not empty
285
+ branch = node_id.select_branch(key)
286
+ return nil, stack if in_node.empty_branch?(branch)
287
+
288
+ # Descend to branch node
289
+ in_node = descend_throw in_node, branch
290
+ if v2?
291
+ if in_node.inner?
292
+ node_id = NodeID.new :depth => in_node.depth,
293
+ :key => in_node.common
294
+ else
295
+ node_id = NodeID.new :depth => 64,
296
+ :key => in_node.key
297
+ end
298
+
299
+ else
300
+ # Get ID of branch node
301
+ node_id = node_id.child_node_id branch
302
+ end
303
+ end
304
+
305
+ # Push final node (assumably corresponding to key)
306
+ stack.push [in_node, node_id]
307
+
308
+ # Return final node (corresponding to key) and stack
309
+ return in_node, stack
310
+ end
311
+
312
+ # Descend to specified branch in parent,
313
+ # throw exception if we cannot
314
+ def descend_throw(parent, branch)
315
+ ret = descend(parent, branch)
316
+ raise Errors::MissingNode, parent.child_hash(branch) if !ret &&
317
+ !parent.empty_branch?(branch)
318
+ ret
319
+ end
320
+
321
+ # Retreive node from nodestore corresponding to
322
+ # specified branch of parent.
323
+ def descend(parent, branch)
324
+ ret = parent.child(branch)
325
+ return ret if ret # || !backed? # TODO (backed)
326
+
327
+ node = fetch_node_nt(parent.child_hash(branch))
328
+ return nil if !node || inconsistent_node?(node)
329
+
330
+ node = parent.canonicalize_child(branch, node)
331
+ node
332
+ end
333
+
334
+ # Returns first leaf node at or below the specified
335
+ # node.
336
+ #
337
+ # @param node to evaluation
338
+ # @param stack ancestor node stack
339
+ # @param branch this node is on
340
+ def first_below(node, stack, branch=0)
341
+ # Return node if node is a leaf
342
+ if node.leaf?
343
+ stack.push [node, node.peek_item.key]
344
+ return node, stack
345
+ end
346
+
347
+ # Append node to ancestry stack for traversal
348
+ if stack.empty?
349
+ stack.push [node, NodeID.new]
350
+
351
+ else
352
+ if v2?
353
+ stack.push [node, NodeID.new(:depth => node.depth,
354
+ :key => node.common)]
355
+
356
+ else
357
+ stack.push [node, stack.last.last.child_node_id(branch)]
358
+ end
359
+ end
360
+
361
+ # Iterate over non-empty branches
362
+ i = 0
363
+ while i < 16
364
+ if !node.empty_branch?(i)
365
+ # descend into branch
366
+ node = descend_throw(node, i)
367
+ raise if stack.empty?
368
+
369
+ # Return first leaf
370
+ if node.leaf?
371
+ stack.push [node, node.peek_item.key]
372
+ return node, stack
373
+ end
374
+
375
+ # Continue tree descent at new level
376
+ if v2?
377
+ stack.push [node, NodeID.new(:depth => node.depth,
378
+ :key => node.common)]
379
+
380
+ else
381
+ stack.push [node, stack.last.last.child_node_id(branch)]
382
+ end
383
+
384
+ i = 0 # scan all 16 branches of this new node
385
+
386
+ else
387
+ i += 1
388
+ end
389
+ end
390
+
391
+ # No node found, return nil and the stack
392
+ return nil, stack
393
+ end
394
+
395
+ public
396
+
397
+ # Returns first directory index in the specified root index
398
+ #
399
+ # @see cdir_next below
400
+ def cdir_first(root_index)
401
+ node = read(root_index)
402
+ raise unless node # never probe for dirs
403
+ cdir_next(root_index, node, 0)
404
+ end
405
+
406
+ # Returns the key of the index in the the node's
407
+ # "indexes" field corresponding to 'dir_entry'.
408
+ #
409
+ # Also returns directory node which contains
410
+ # key of the node being returned.
411
+ #
412
+ # Also returns dir_entry index of next record in
413
+ # directory node.
414
+ #
415
+ # This method handles the special case where dir_entry
416
+ # is greater than the local indexes size but the
417
+ # 'index_next' is also set. In this case, index
418
+ # traversal will continue on the next SLE node
419
+ # whose lookup key is calculated from the root
420
+ # index and 'index_next' value. In this case
421
+ # the directory node and next dir_entry will be
422
+ # set appropriately and returned.
423
+ #
424
+ # @param root_index top level index of the tree
425
+ # being traversed.
426
+ # @param node SLE containing 'indexes' field from
427
+ # which the 'dir_entry'th index will be returned
428
+ # @param dir_entry numerical array index to return
429
+ # from 'indexes'
430
+ def cdir_next(root_index, node, dir_entry)
431
+ indexes = node.field(:vector256, :indexes)
432
+ raise unless dir_entry <= indexes.size
433
+
434
+ if dir_entry >= indexes.size
435
+ nxt = node.field(:uint64, :index_next)
436
+ return nil unless nxt
437
+
438
+ nxt = read(NodeStore::Indexes::page(root_index, nxt))
439
+ return nil unless nxt
440
+
441
+ return cdir_next(root_index, nxt, 0)
442
+ end
443
+
444
+ return indexes[dir_entry], node, (dir_entry + 1)
445
+ end
446
+ end # class SHAMap
447
+ end # module XRBP