xrbp 0.1.3 → 0.1.4

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