xrbp 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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