xrbp 0.1.3 → 0.1.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 30fb389dec1e019a299501a035cb5131332fec5b3cc192f24be9ad280aaf60e4
4
- data.tar.gz: 8bec54f51b34ebbee7a2f433191d6dbe72e07fb3d2c60c1d3caa0b818c4da1f8
3
+ metadata.gz: e0ce0d6d6704d00d7fe16278ad96bfd68514f73e7b76486894ae479dee90eef8
4
+ data.tar.gz: 149a45a103104f1e5cef4da27d6870ba26f98dcb96a4f7e6e0e1bc18038d7367
5
5
  SHA512:
6
- metadata.gz: f62bb3b3c347d42c29f227afd7398257078b6a30996a82f06d0952a337dcea495b33707a8430432c9945214d0609efde2c73a37472a23dfdeba9ab4ee13aebe3
7
- data.tar.gz: d156f474e8d54a4583f3b41516caa9fb6e55d1c1d7edc6d04838f7066e93a560b48485880bdd2b1cf15b7aeb05c93087542dbf1ad80e5f8620a24dee16592e5f
6
+ metadata.gz: ed3630f368f3706c6c87e7031d25a6c3136489d6de8e3c83120c8a638a9e198d376615ed0c2fe7f5dab1f897f6aff6502a0296b0d3e03ea4ec27813d9d62b027
7
+ data.tar.gz: f43f8de4dc245cd2b3fe5996fd8a33f5b009ba60ea08858d0e1af9b008a4bfe71855172e734699d32c1b1dc97d853e94285efb4f11a94feac4f2c5b0087754eb
@@ -25,7 +25,7 @@ connection.on :peers do |node, peers|
25
25
  end
26
26
 
27
27
  connection.on :peer do |node, peer|
28
- puts " #{peer.url}"
28
+ print " #{peer.url.ljust(40)} - Ledgers: #{peer.ledgers}\n"
29
29
  end
30
30
 
31
31
  XRBP::Model::Node.crawl("wss://s1.ripple.com:51235",
@@ -0,0 +1,14 @@
1
+ $: << File.expand_path('../../lib', __FILE__)
2
+ require 'xrbp'
3
+ require 'xrbp/nodestore/backends/rocksdb'
4
+
5
+ db = XRBP::NodeStore::Backends::RocksDB.new "/var/lib/rippled/rocksdb/rippledb.0899"
6
+
7
+ #ledger = "B506ADD630CB707044B4BFFCD943C1395966692A13DD618E5BD0978A006B43BD"
8
+ #ledger = [ledger].pack("H*")
9
+ #puts db.ledger(ledger)
10
+
11
+ #account = "0001bf7468341666f1f47a95e0f4d88e68b5fc7d20d77437cb22954fbbfe6127"
12
+ account = "02c46b3a4130d0a329c47f0da61b829aa5d1ae53c5817e475bcd794e5107be44"
13
+ account = [account].pack("H*")
14
+ puts db.account(account)
@@ -0,0 +1,42 @@
1
+ $: << File.expand_path('../../lib', __FILE__)
2
+ require 'xrbp'
3
+ require 'xrbp/nodestore/backends/rocksdb'
4
+
5
+ db = XRBP::NodeStore::Backends::RocksDB.new "/var/lib/rippled/rocksdb/rippledb.0899"
6
+
7
+ db.on :unknown do |hash, node|
8
+ puts "Unknown #{hash}: #{node}"
9
+ end
10
+
11
+ db.on :inner_node do |hash, node|
12
+ #puts "Inner Node #{hash}"
13
+ end
14
+
15
+ db.on :ledger do |hash, ledger|
16
+ #puts "Ledger #{ledger['index']}"
17
+ end
18
+
19
+ db.on :tx do |hash, tx|
20
+ puts "Tx #{tx}"
21
+ end
22
+
23
+ db.on :account do |hash, account|
24
+ #puts "Account #{account}"
25
+ end
26
+
27
+ ###
28
+
29
+ tallys = {}
30
+
31
+ # object iterator invokes event emitters
32
+ db.each do |node|
33
+ obj = XRBP::NodeStore::Format::TYPE_INFER.decode(node.value)
34
+ node_type = XRBP::NodeStore::Format::NODE_TYPES[obj["node_type"]]
35
+ hash_prefix = XRBP::NodeStore::Format::HASH_PREFIXES[obj["hash_prefix"].upcase]
36
+
37
+ type = node_type.to_s + "/" + hash_prefix.to_s
38
+ tallys[type] ||= 0
39
+ tallys[type] += 1
40
+ end
41
+
42
+ puts tallys
data/examples/p2p.rb ADDED
@@ -0,0 +1,14 @@
1
+ gem 'openssl', '2.1.3'
2
+
3
+ $: << File.expand_path('../../lib', __FILE__)
4
+ require 'xrbp'
5
+
6
+ overlay = XRBP::Overlay::Connection.new "127.0.0.1", 51235
7
+ overlay.connect
8
+ puts overlay.handshake.response
9
+
10
+ overlay.read_frames do |frame|
11
+ puts "Message: #{frame.type_name} (#{frame.size} bytes)"
12
+ end
13
+
14
+ overlay.close
data/lib/xrbp.rb CHANGED
@@ -6,5 +6,8 @@ require 'xrbp/terminatable'
6
6
  require 'xrbp/plugins'
7
7
  require 'xrbp/websocket'
8
8
  require 'xrbp/webclient'
9
+ require 'xrbp/crypto'
10
+ require 'xrbp/nodestore'
11
+ require 'xrbp/overlay'
9
12
  require 'xrbp/model'
10
13
  require 'xrbp/dsl'
data/lib/xrbp/common.rb CHANGED
@@ -7,4 +7,10 @@ module XRBP
7
7
  # Created on 2013-01-01
8
8
  # https://data.ripple.com/v2/ledgers/32570
9
9
  GENESIS_TIME = DateTime.new(2013, 1, 1, 0, 0, 0)
10
+
11
+ # Convert XRP Ledger time to local time
12
+ def self.from_xrp_time(xrp_time)
13
+ return nil if xrp_time.nil?
14
+ Time.at(xrp_time + 946684800)
15
+ end
10
16
  end # module XRBP
data/lib/xrbp/core_ext.rb CHANGED
@@ -22,3 +22,25 @@ class Queue
22
22
  end
23
23
  end
24
24
  end
25
+
26
+ # @private
27
+ class String
28
+ # return bignum corresponding to string
29
+ def to_bn
30
+ bytes.inject(0) { |bn, b| (bn << 8) | b }
31
+ end
32
+ end
33
+
34
+ # @private
35
+ class Integer
36
+ # return bytes
37
+ def bytes
38
+ i = dup
39
+ b = []
40
+ until i == 0
41
+ b << (i & 0xFF)
42
+ i = i >> 8
43
+ end
44
+ b
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ require 'xrbp/crypto/key'
2
+ require 'xrbp/crypto/account'
3
+ require 'xrbp/crypto/node'
@@ -0,0 +1,33 @@
1
+ require 'base58'
2
+
3
+ module XRBP
4
+ module Crypto
5
+ def self.account(key=nil)
6
+ pub = nil
7
+ if key == :secp256k1 || key.nil?
8
+ key = Key::secp256k1
9
+ key[:type] = :secp256k1
10
+ pub = key[:public]
11
+
12
+ elsif key == :ed25519
13
+ key = Key::ed25519
14
+ key[:type] = :ed25519
15
+ pub = "\xED" + key[:public]
16
+
17
+ elsif key.is_a?(Hash)
18
+ pub = key[:public]
19
+
20
+ else
21
+ pub = key
22
+ key = {:public => pub}
23
+ end
24
+
25
+ sha256 = OpenSSL::Digest::SHA256.new
26
+ ripemd160 = OpenSSL::Digest::RIPEMD160.new
27
+ account_id = [Key::TOKEN_TYPES[:account_id]].pack("C") + ripemd160.digest(sha256.digest([pub].pack("H*")))
28
+ chksum = sha256.digest(sha256.digest(account_id))[0..3]
29
+
30
+ { :account => Base58.binary_to_base58(account_id + chksum, :ripple) }.merge(key)
31
+ end
32
+ end # module Crypto
33
+ end # module XRBP
@@ -0,0 +1,93 @@
1
+ require 'securerandom'
2
+
3
+ module XRBP
4
+ module Crypto
5
+ module Key
6
+ TOKEN_TYPES = {
7
+ :none => 1, # unused
8
+ :node_public => 28,
9
+ :node_private => 32,
10
+ :account_id => 0,
11
+ :account_public => 35,
12
+ :account_secret => 34,
13
+ :family_generator => 41,
14
+ :family_seed => 33
15
+ }
16
+
17
+ ###
18
+
19
+ def self.priv
20
+ seed = SecureRandom.random_bytes(32)
21
+ OpenSSL::Digest::SHA256.new.digest(seed)
22
+ end
23
+
24
+ def self.secp256k1
25
+ # XXX: the bitcoin secp256k1 implementation (which rippled pulls in / vendors)
26
+ # has alot of nuances which require special configuration in openssl. For
27
+ # the time being, mitigate this by pulling in & using the ruby
28
+ # btc-secp256k1 bindings:
29
+ # https://github.com/cryptape/ruby-bitcoin-secp256k1
30
+ #
31
+ # Perhaps at some point, we can look into implementing this logic in pure-ruby:
32
+ # https://medium.com/coinmonks/introduction-to-blockchains-bedrock-the-elliptic-curve-secp256k1-e4bd3bc17d
33
+ require 'secp256k1'
34
+
35
+ spk = Secp256k1::PrivateKey.new
36
+
37
+ # XXX: I'd like to generate the private key first & set,
38
+ # but for some reason this doesn't work. When the
39
+ # keys are loaded for signing/verification later
40
+ # the public key is not able to verify the signature
41
+ # generated from the private key set in this way.
42
+ # TODO: Investigate
43
+ #pk = priv
44
+ #spk.set_raw_privkey [pk].pack("H*")
45
+
46
+ { :public => spk.pubkey.serialize.unpack("H*").first,
47
+ :private => spk.send(:serialize) }
48
+ end
49
+
50
+ def self.ed25519
51
+ # XXX openssl 1.1.1 needed for EdDSA support:
52
+ # https://www.openssl.org/blog/blog/2018/09/11/release111/
53
+ # Until then use this:
54
+ require "ed25519"
55
+
56
+ # FIXME: this works for now (eg generates valid keys),
57
+ # but we should do this in the same way rippled does:
58
+ # Generate private key, then generate corresponding
59
+ # Ed25519 public key
60
+ key = Ed25519::SigningKey.generate
61
+ { :public => key.to_bytes.unpack("H*").first.upcase,
62
+ :private => key.verify_key.to_bytes.unpack("H*").first.upcase }
63
+ end
64
+
65
+ ###
66
+
67
+ def self.sign_digest(key, data)
68
+ raise "unknown key" unless key.is_a?(Hash) && key[:type] && key[:private]
69
+ raise "invalid data" unless data.length == 32
70
+
71
+
72
+ if key[:type] == :secp256k1
73
+ # XXX: see note about this library above
74
+ require 'secp256k1'
75
+
76
+ pk = Secp256k1::PrivateKey.new
77
+ pk.set_raw_privkey [key[:private]].pack("H*")
78
+ #pk.pubkey.deserialize [key[:public]].pack("H*")
79
+ sig_raw = pk.ecdsa_sign data, raw: true
80
+ return pk.ecdsa_serialize sig_raw
81
+
82
+ #elsif key[:type] == :ed25519
83
+ # TODO
84
+ end
85
+
86
+ raise "unknown key type"
87
+ end
88
+
89
+ def self.verify(key, data, expected)
90
+ end
91
+ end
92
+ end # module Crypto
93
+ end # module XRBP
@@ -0,0 +1,27 @@
1
+ require 'base58'
2
+
3
+ module XRBP
4
+ module Crypto
5
+ def self.node(key=nil)
6
+ pub = nil
7
+ if key == :secp256k1 || key.nil?
8
+ key = Key::secp256k1
9
+ key[:type] = :secp256k1
10
+ pub = key[:public]
11
+
12
+ elsif key.is_a?(Hash)
13
+ pub = key[:public]
14
+
15
+ else
16
+ pub = key
17
+ key = {:public => pub}
18
+ end
19
+
20
+ sha256 = OpenSSL::Digest::SHA256.new
21
+ node_id = [Key::TOKEN_TYPES[:node_public]].pack("C") + [pub].pack("H*")
22
+ chksum = sha256.digest(sha256.digest(node_id))[0..3]
23
+
24
+ { :node => Base58.binary_to_base58(node_id + chksum, :ripple) }.merge(key)
25
+ end
26
+ end # module Crypto
27
+ end # module XRBP
@@ -11,7 +11,7 @@ module XRBP
11
11
  DEFAULT_CRAWL_PORT = 51235
12
12
 
13
13
  attr_accessor :ip, :port
14
- attr_accessor :addr, :version, :uptime, :type
14
+ attr_accessor :addr, :version, :uptime, :type, :ledgers
15
15
 
16
16
  # Return unique node id
17
17
  def id
@@ -71,6 +71,7 @@ module XRBP
71
71
  n.version = p["version"].split("-").last
72
72
  n.uptime = p["uptime"]
73
73
  n.type = p["type"]
74
+ n.ledgers = p["complete_ledgers"]
74
75
 
75
76
  n
76
77
  end
@@ -0,0 +1,7 @@
1
+ require_relative './nodestore/format'
2
+
3
+ require_relative './nodestore/db'
4
+
5
+ # optionally include:
6
+ #require_relative './nodestore/backends/rocksdb'
7
+ #require_relative './nodestore/backends/nudb'
@@ -0,0 +1 @@
1
+ # TODO
@@ -0,0 +1,43 @@
1
+ require "rocksdb"
2
+
3
+ module XRBP
4
+ module NodeStore
5
+ module Backends
6
+ class RocksDB < DB
7
+ # cap max open files for performance
8
+ MAX_OPEN_FILES = 200
9
+
10
+ def initialize(path)
11
+ @db = ::RocksDB::DB.new path,
12
+ {:readonly => true,
13
+ :max_open_files => MAX_OPEN_FILES}
14
+ end
15
+
16
+ def [](key)
17
+ @db[key]
18
+ end
19
+
20
+ def each
21
+ iterator = @db.new_iterator
22
+ iterator.seek_to_first
23
+
24
+ while(iterator.valid)
25
+ type, obj = infer_type(iterator.value)
26
+
27
+ if type
28
+ emit type, iterator.key, obj
29
+ else
30
+ emit :unknown, iterator.key,
31
+ iterator.value
32
+ end
33
+
34
+ yield iterator
35
+ iterator.next
36
+ end
37
+
38
+ iterator.close
39
+ end
40
+ end # class RocksDB
41
+ end # module Backends
42
+ end # module NodeStore
43
+ end # module XRBP
@@ -0,0 +1,324 @@
1
+ require 'base58'
2
+ require 'openssl'
3
+
4
+ module XRBP
5
+ module NodeStore
6
+ class DB
7
+ include Enumerable
8
+ include EventEmitter
9
+
10
+ def ledger(hash)
11
+ parse_ledger(self[hash])
12
+ end
13
+
14
+ def account(hash)
15
+ parse_ledger_entry(self[hash])
16
+ end
17
+
18
+ def tx(hash)
19
+ parse_tx(self[hash])
20
+ end
21
+
22
+ def inner_node(hash)
23
+ parse_inner_node(self[hash])
24
+ end
25
+
26
+ ###
27
+
28
+ private
29
+
30
+ def parse_ledger(ledger)
31
+ obj = Format::LEDGER.decode(ledger)
32
+ obj['close_time'] = XRBP::from_xrp_time(obj['close_time']).utc
33
+ obj['parent_close_time'] = XRBP::from_xrp_time(obj['parent_close_time']).utc
34
+ obj['parent_hash'].upcase!
35
+ obj['tx_hash'].upcase!
36
+ obj['account_hash'].upcase!
37
+ obj
38
+ end
39
+
40
+ def parse_encoding(encoding)
41
+ enc = encoding.unpack("C").first
42
+ type = enc >> 4
43
+ field = enc & 0xF
44
+ encoding = encoding[1..-1]
45
+
46
+ if type == 0
47
+ type = encoding.unpack("C").first
48
+ encoding = encoding[1..-1]
49
+ end
50
+
51
+ if field == 0
52
+ field = encoding.unpack("C").first
53
+ encoding = encoding[1..-1]
54
+ end
55
+
56
+ type = Format::SERIALIZED_TYPES[type]
57
+ [[type, field], encoding]
58
+ end
59
+
60
+ def parse_ledger_entry(ledger_entry)
61
+ # validate parsability
62
+ obj = Format::TYPE_INFER.decode(ledger_entry)
63
+ node_type = Format::NODE_TYPES[obj["node_type"]]
64
+ hash_prefix = Format::HASH_PREFIXES[obj["hash_prefix"].upcase]
65
+ raise unless node_type == :account_node &&
66
+ hash_prefix == :leaf_node
67
+
68
+ # discard node type, and hash prefix
69
+ ledger_entry = ledger_entry[13..-1]
70
+
71
+ # verify encoding
72
+ encoding, ledger_entry = parse_encoding(ledger_entry)
73
+ raise "Invalid Ledger Entry" unless Format::ENCODINGS[encoding] == :ledger_entry_type
74
+ ledger_entry = ledger_entry.bytes
75
+
76
+ # first byte after encoding is ledger entry type prefix
77
+ prefix = ledger_entry[0..1].pack("C*")
78
+
79
+ # last 32 bytes is entry index
80
+ index = ledger_entry[-32..-1].pack("C*")
81
+ .unpack("H*")
82
+ .first
83
+ .upcase
84
+
85
+ # remaining bytes are serialized object
86
+ fields = parse_fields(ledger_entry[2...-32].pack("C*"))
87
+
88
+ # TODO instantiate class corresponding to prefix &
89
+ # populate attributes w/ fields
90
+
91
+ { :prefix => prefix,
92
+ :index => index,
93
+ :fields => fields }
94
+ end
95
+
96
+ ###
97
+
98
+ def parse_fields(fields)
99
+ parsed = {}
100
+ until fields == "" || fields.nil?
101
+ encoding, fields = parse_encoding(fields)
102
+ return parsed if encoding.first.nil?
103
+
104
+ e = Format::ENCODINGS[encoding]
105
+ value, fields = parse_field(fields, encoding)
106
+ break unless value
107
+ parsed[e] = value
108
+ end
109
+
110
+ return parsed
111
+ end
112
+
113
+ def parse_field(data, encoding)
114
+ length = encoding.first
115
+
116
+ case length
117
+ when :uint8
118
+ return data.unpack("C").first, data[1..-1]
119
+ when :uint16
120
+ return data.unpack("S").first, data[2..-1]
121
+ when :uint32
122
+ return data.unpack("L").first, data[4..-1]
123
+ when :uint64
124
+ return data.unpack("Q").first, data[8..-1]
125
+ when :hash128
126
+ return data.unpack("H32").first, data[16..-1]
127
+ when :hash160
128
+ return data.unpack("H40").first, data[20..-1]
129
+ when :hash256
130
+ return data.unpack("H64").first, data[32..-1]
131
+
132
+ when :amount
133
+ amount = data[0..7].unpack("Q>").first
134
+ xrp = amount < 0x8000000000000000
135
+ return (amount & 0x3FFFFFFFFFFFFFFF), data[8..-1] if xrp
136
+
137
+ sign = (amount & 0x4000000000000000) >> 62 # 0 = neg / 1 = pos
138
+ exp = (amount & 0x3FC0000000000000) >> 54
139
+ mant = (amount & 0x003FFFFFFFFFFFFF)
140
+
141
+ data = data[8..-1]
142
+ currency = Format::CURRENCY_CODE.decode(data)
143
+
144
+ data = data[Format::CURRENCY_CODE.size..-1]
145
+ issuer, data = parse_account(data, 20)
146
+
147
+ # TODO calculate value
148
+ return { :sign => sign,
149
+ :exp => exp,
150
+ :mantissa => mant,
151
+ :currency => currency,
152
+ :issuer => issuer }, data
153
+
154
+ when :vl
155
+ vl, offset = parse_vl(data)
156
+ return data[offset..vl+offset-1], data[vl+offset..-1]
157
+
158
+ when :account
159
+ return parse_account(data)
160
+
161
+ when :array
162
+ e = Format::ENCODINGS[encoding]
163
+ return nil, data if e == :end_of_array
164
+
165
+ array = []
166
+ until data == "" || data.nil?
167
+ aencoding, data = parse_encoding(data)
168
+ break if aencoding.first.nil?
169
+
170
+ e = Format::ENCODINGS[aencoding]
171
+ break if e == :end_of_array
172
+
173
+ value, data = parse_field(data, aencoding)
174
+ break unless value
175
+ array << value
176
+ end
177
+
178
+ return array, data
179
+
180
+ when :object
181
+ e = Format::ENCODINGS[encoding]
182
+ case e
183
+ when :end_of_object
184
+ return nil, data
185
+
186
+ when :signer, :signer_entry,
187
+ :majority, :memo,
188
+ :modified_node, :created_node, :deleted_node,
189
+ :previous_fields, :final_fields, :new_fields
190
+ # TODO instantiate corresponding classes
191
+ return parse_fields(data)
192
+
193
+ #else:
194
+ end
195
+
196
+ when :pathset
197
+ pathset = []
198
+ until data == "" || data.nil?
199
+ segment = data.unpack("C").first
200
+ data = data[1..-1]
201
+ return pathset, data if segment == 0x00 # end of path
202
+
203
+ if segment == 0xFF # path boundry
204
+ pathset << []
205
+ else
206
+ if segment & 0x01 # path account
207
+ issuer, data = parse_account(data, 20)
208
+ end
209
+
210
+ if segment & 0x02 # path currency
211
+ currency = Format::CURRENCY_CODE.decode(data)
212
+ data = data[Format::CURRENCY_CODE.size..-1]
213
+ end
214
+
215
+ if segment & 0x03 # path issuer
216
+ issuer, data = parse_account(data, 20)
217
+ end
218
+ end
219
+ end
220
+
221
+ return pathset, data
222
+
223
+ when :vector256
224
+ vl, offset = parse_vl(data)
225
+ return data[offset..vl+offset-1], data[vl+offset..-1]
226
+
227
+ end
228
+
229
+ raise
230
+ end
231
+
232
+ def parse_vl(data)
233
+ data = data.bytes
234
+ first = data.first.to_i
235
+ return first, 1 if first <= 192
236
+
237
+ data = data[1..-1]
238
+ second = data.first.to_i
239
+ if first <= 240
240
+ return (193+(first-193)*256+second), 2
241
+
242
+ elsif first <= 254
243
+ data = data[1..-1]
244
+ third = data.first.to_i
245
+ return (12481 + (first-241)*65536 + second*256 + third), 3
246
+ end
247
+
248
+ raise
249
+ end
250
+
251
+ def parse_account(data, vl=nil)
252
+ unless vl
253
+ vl,offset = parse_vl(data)
254
+ data = data[offset..-1]
255
+ end
256
+
257
+ acct = "\0" + data[0..vl-1]
258
+ sha256 = OpenSSL::Digest::SHA256.new
259
+ digest = sha256.digest(sha256.digest(acct))[0..3]
260
+ acct += digest
261
+ return Base58.binary_to_base58(acct, :ripple), data[vl..-1]
262
+ end
263
+
264
+ ###
265
+
266
+ def parse_tx(tx)
267
+ obj = Format::TYPE_INFER.decode(tx)
268
+ node_type = Format::NODE_TYPES[obj["node_type"]]
269
+ hash_prefix = Format::HASH_PREFIXES[obj["hash_prefix"].upcase]
270
+ raise unless node_type == :tx_node &&
271
+ hash_prefix == :tx_node
272
+
273
+ # discard node type, and hash prefix
274
+ tx = tx[13..-1]
275
+
276
+ # get node length
277
+ vl, offset = parse_vl(tx)
278
+ node, _tx = tx.bytes[offset..vl+offset-1], tx.bytes[vl+offset..-1]
279
+ node = parse_fields(node.pack("C*"))
280
+
281
+ # get meta length
282
+ vl, offset = parse_vl(_tx.pack("C*"))
283
+ meta, index = _tx[offset..vl+offset-1], _tx[vl+offset..-1]
284
+ meta = parse_fields(meta.pack("C*"))
285
+
286
+ { :node => node,
287
+ :meta => meta,
288
+ :index => index.pack("C*").unpack("H*").first.upcase }
289
+ end
290
+
291
+ def parse_inner_node(node)
292
+ # verify parsability
293
+ obj = Format::TYPE_INFER.decode(node)
294
+ hash_prefix = Format::HASH_PREFIXES[obj["hash_prefix"].upcase]
295
+ raise unless hash_prefix == :inner_node
296
+
297
+ Format::INNER_NODE.decode(node)
298
+ end
299
+
300
+ protected
301
+
302
+ def infer_type(value)
303
+ obj = Format::TYPE_INFER.decode(value)
304
+ node_type = Format::NODE_TYPES[obj["node_type"]]
305
+ hash_prefix = Format::HASH_PREFIXES[obj["hash_prefix"].upcase]
306
+
307
+ if hash_prefix == :inner_node
308
+ return :inner_node, parse_inner_node(value)
309
+
310
+ elsif node_type == :account_node
311
+ return :account, parse_ledger_entry(value)
312
+
313
+ elsif node_type == :tx_node
314
+ return :tx, parse_tx(value)
315
+
316
+ elsif node_type == :ledger
317
+ return :ledger, parse_ledger(value)
318
+ end
319
+
320
+ return nil
321
+ end
322
+ end # class DB
323
+ end # module NodeStore
324
+ end # module XRBP