xrbp 0.2.2 → 0.2.3

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 (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
@@ -0,0 +1,49 @@
1
+ module XRBP
2
+ module NodeStore
3
+ class STAmount
4
+ module Comparison
5
+ def <(o)
6
+ return self < STAmount.new(:mantissa => o) if o.kind_of?(Numeric)
7
+
8
+ return neg if neg && !o.neg
9
+ if mantissa == 0
10
+ return false if o.neg
11
+ return o.mantissa != 0
12
+ end
13
+
14
+ return false if o.mantissa == 0
15
+ return neg if exponent > o.exponent
16
+ return !neg if exponent < o.exponent
17
+ return neg if mantissa > o.mantissa
18
+ return !neg if mantissa < o.mantissa
19
+
20
+ return false
21
+ end
22
+
23
+ def >=(o)
24
+ !(self < o)
25
+ end
26
+
27
+ def >(o)
28
+ self >= o && self != o
29
+ end
30
+
31
+ def ==(o)
32
+ return self == STAmount.new(:mantissa => o) if o.kind_of?(Numeric)
33
+
34
+ neg == o.neg &&
35
+ mantissa == o.mantissa &&
36
+ exponent == o.exponent
37
+ end
38
+
39
+ def <=>(o)
40
+ return self <=> STAmount.new(:mantissa => o) if o.kind_of?(Numeric)
41
+
42
+ return 0 if self == o
43
+ return -1 if self < o
44
+ return 1 if self > o
45
+ end
46
+ end # module Comparison
47
+ end # class STAmount
48
+ end # module NodeStore
49
+ end # module XRBP
@@ -0,0 +1,203 @@
1
+ module XRBP
2
+ module NodeStore
3
+ class STAmount
4
+ module Conversion
5
+ module ClassMethods
6
+ # @see {NodeStore::Parser::parse_amount}
7
+ def from_wire(data)
8
+ native = (data & STAmount::NOT_NATIVE) == 0
9
+ neg = (data & ~STAmount::NOT_NATIVE & STAmount::POS_NATIVE) == 0
10
+ value = (data & ~STAmount::NOT_NATIVE & ~STAmount::POS_NATIVE)
11
+
12
+ if native
13
+ STAmount.new :issue => NodeStore.xrp_issue,
14
+ :neg => neg,
15
+ :mantissa => value
16
+ else
17
+ exp = (value >> 54) - 97
18
+ mant = value & 0x3fffffffffffff
19
+ STAmount.new :neg => neg,
20
+ :exponent => exp,
21
+ :mantissa => mant
22
+ end
23
+ end
24
+
25
+ # Convert string to STAmount
26
+ #
27
+ # @see STAmount#amountFromString (in rippled)
28
+ def parse(str, issue=nil)
29
+ match = "^"+ # the beginning of the string
30
+ "([-+]?)"+ # (optional) + or - character
31
+ "(0|[1-9][0-9]*)"+ # a number (no leading zeroes, unless 0)
32
+ "(\\.([0-9]+))?"+ # (optional) period followed by any number
33
+ "([eE]([+-]?)([0-9]+))?"+ # (optional) E, optional + or -, any number
34
+ "$"
35
+ match = Regexp.new(match)
36
+ match = str.match(match)
37
+ raise "Number '#{str}' is not valid" unless match
38
+
39
+ # Match fields:
40
+ #
41
+ # 0 = whole input
42
+ # 1 = sign
43
+ # 2 = integer portion
44
+ # 3 = whole fraction (with '.')
45
+ # 4 = fraction (without '.')
46
+ # 5 = whole exponent (with 'e')
47
+ # 6 = exponent sign
48
+ # 7 = exponent number
49
+
50
+ raise "Number '#{str}' is overlong" if ((match[2] || "").length +
51
+ (match[4] || "").length) > 32
52
+
53
+ neg = !!match[1] && match[1] == '-'
54
+
55
+ raise "XRP must be specified in integral drops" if issue && issue.xrp? && !!match[3]
56
+
57
+ mantissa = 0
58
+ exponent = 0
59
+
60
+ if !match[4]
61
+ # integral only
62
+ mantissa = match[2].to_i
63
+
64
+ else
65
+ # integer and fraction
66
+ mantissa = (match[2] + match[4]).to_i
67
+ exponent = -(match[4].length)
68
+ end
69
+
70
+ if !!match[5]
71
+ # exponent
72
+ if match[6] && match[6] == '-'
73
+ exponent -= match[7].to_i
74
+ else
75
+ exponent += match[7].to_i
76
+ end
77
+ end
78
+
79
+ return STAmount.new :issue => issue,
80
+ :mantissa => mantissa,
81
+ :exponent => exponent,
82
+ :neg => neg
83
+ end
84
+ end
85
+
86
+ def self.included(base)
87
+ base.extend(ClassMethods)
88
+ end
89
+
90
+ # Encode STAmount into binary format
91
+ def to_wire
92
+ xrp_bit = ((native? ? 0 : 1) << 63)
93
+ neg_bit = (( neg ? 0 : 1) << 62)
94
+ value_bits = native? ? mantissa :
95
+ (((exponent+97) << 54) + mantissa)
96
+
97
+ xrp_bit + neg_bit + value_bits
98
+ end
99
+
100
+ ###
101
+
102
+ def to_h
103
+ {:mantissa => mantissa,
104
+ :exponent => exponent,
105
+ :neg => neg,
106
+ :issue => issue.to_h}
107
+ end
108
+
109
+ def negate!
110
+ return if zero?
111
+ @neg = !@neg
112
+ end
113
+
114
+ ###
115
+
116
+ protected
117
+
118
+ def canonicalize
119
+ if native?
120
+ if @mantissa == 0
121
+ @exponent = 0
122
+ @neg = false
123
+ return
124
+ end
125
+
126
+ while @exponent < 0
127
+ @mantissa /= 10
128
+ @exponent += 1
129
+ end
130
+
131
+ while @exponent > 0
132
+ @mantissa *= 10
133
+ @exponent -= 1
134
+ end
135
+
136
+ raise if @mantissa > MAX_NATIVE
137
+ return
138
+ end
139
+
140
+ if @mantissa == 0
141
+ @exponent = -100
142
+ @negative = false
143
+ return
144
+ end
145
+
146
+ while ((@mantissa < MIN_VAL) && (@exponent > MIN_OFFSET))
147
+ @mantissa *= 10;
148
+ @exponent -= 1
149
+ end
150
+
151
+ while (@mantissa > MAX_VAL)
152
+ raise "value overflow" if (@exponent >= MAX_OFFSET)
153
+
154
+ @mantissa /= 10
155
+ @exponent += 1
156
+ end
157
+
158
+ if @exponent < MIN_OFFSET || @mantissa < MIN_VAL
159
+ @mantissa = 0;
160
+ @neg = false;
161
+ @exponent = -100;
162
+ return
163
+ end
164
+
165
+ raise "value overflow" if (@exponent > MAX_OFFSET)
166
+
167
+ raise unless @mantissa == 0 || (@mantissa >= MIN_VAL && @mantissa <= MAX_VAL)
168
+ raise unless @mantissa == 0 || (@exponent >= MIN_OFFSET && @exponent <= MAX_OFFSET)
169
+ raise unless @mantissa != 0 || @exponent != -100
170
+ end
171
+
172
+ public
173
+
174
+ def clear
175
+ # From rippled docs:
176
+ # The -100 is used to allow 0 to sort less than a small positive values
177
+ # which have a negative exponent.
178
+ @exponent = native? ? 0 : -100
179
+
180
+ @neg = false
181
+ @mantissa = 0
182
+ end
183
+
184
+ def sn_value
185
+ neg ? (-mantissa) : mantissa
186
+ end
187
+
188
+ ###
189
+
190
+ # In drops!
191
+ def xrp_amount
192
+ neg ? (-value) : value
193
+ end
194
+
195
+ alias :drops :xrp_amount
196
+
197
+ def iou_amount
198
+ (neg ? -1 : 1) * mantissa * 10 ** exponent
199
+ end
200
+ end # module Conversion
201
+ end # class STAmount
202
+ end # module NodeStore
203
+ end # module XRBP
@@ -2,22 +2,87 @@ require 'sqlite3'
2
2
 
3
3
  module XRBP
4
4
  module NodeStore
5
+ # Wraps sqlite3 database created/maintianed by rippled. Allows client
6
+ # to query for data stored in sql database.
5
7
  class SQLDB
8
+
9
+ # SQL DB intializer
10
+ #
11
+ # @param dir [String] directory containing binary nodestore. For consistency
12
+ # with other nodestore paths this should be set to the directory containing
13
+ # the actual 'nudb' or 'rocksdb' datafiles, as the sqlite3 databases will be
14
+ # inferred from the parent directory.
6
15
  def initialize(dir)
7
16
  @dir = dir
8
17
  end
9
18
 
10
19
  def ledger_db
11
- @ledger_db ||= SQLite3::Database.new File.join(@dir, "ledger.db")
20
+ @ledger_db ||= SQLite3::Database.new File.join(@dir, "..", "ledger.db")
12
21
  end
13
22
 
14
23
  def tx_db
15
- @ledger_db ||= SQLite3::Database.new File.join(@dir, "transaction.db")
24
+ @ledger_db ||= SQLite3::Database.new File.join(@dir, "..", "transaction.db")
16
25
  end
17
26
 
18
- def ledger_hash_for_seq(seq)
19
- ledger_db.execute("select LedgerHash from ledgers where LedgerSeq = ?", seq).first.first
27
+ def ledgers
28
+ @ledgers ||= Ledgers.new(self)
20
29
  end
30
+
31
+ class Ledgers
32
+ include Enumerable
33
+
34
+ def initialize(sql_db)
35
+ @sql_db = sql_db
36
+ end
37
+
38
+ def between(before, after)
39
+ @sql_db.ledger_db.execute("select * from ledgers where ClosingTime >= ? and ClosingTime <= ?",
40
+ before.to_xrp_time,
41
+ after.to_xrp_time)
42
+ .collect { |row| from_db(row) }
43
+ end
44
+
45
+ def hash_for_seq(seq)
46
+ @sql_db.ledger_db.execute("select LedgerHash from ledgers where LedgerSeq = ?", seq).first.first
47
+ end
48
+
49
+ def size
50
+ @sql_db.ledger_db.execute("select count(*) from ledgers").first.first
51
+ end
52
+
53
+ alias :count :size
54
+
55
+ def each
56
+ all.each do |row|
57
+ yield row
58
+ end
59
+ end
60
+
61
+ def last
62
+ all.last
63
+ end
64
+
65
+ # TODO: remove memoization, define first(n), last(n) methods
66
+ def all
67
+ @all ||= @sql_db.ledger_db.execute("select * from ledgers order by LedgerSeq asc")
68
+ .collect { |row| from_db(row) }
69
+ end
70
+
71
+ private
72
+
73
+ def from_db(row)
74
+ {:hash => row[0],
75
+ :seq => row[1],
76
+ :prev_hash => row[2],
77
+ :total_coins => row[3],
78
+ :closing_time => row[4].from_xrp_time,
79
+ :prev_closing_time => row[5].from_xrp_time,
80
+ :close_time_res => row[6],
81
+ :close_flags => row[7],
82
+ :account_set_hash => row[8],
83
+ :trans_set_hash => row[9]}
84
+ end
85
+ end # class Ledgers
21
86
  end # class SQLDB
22
87
  end # module NodeStore
23
88
  end # module XRBP
@@ -44,7 +44,7 @@ module XRBP
44
44
  sf = sha512.digest(sf)
45
45
  pf = sha512.digest(pf)
46
46
  shared = sf.to_bn ^ pf.to_bn
47
- shared = shared.bytes.reverse.pack("C*")
47
+ shared = shared.byte_string
48
48
  shared = sha512.digest(shared)[0..31]
49
49
 
50
50
  shared = Crypto::Key.sign_digest(node, shared)
@@ -1,3 +1,3 @@
1
1
  module XRBP
2
- VERSION = '0.2.2'
2
+ VERSION = '0.2.3'
3
3
  end # module XRBP
@@ -1,16 +1,21 @@
1
1
  describe XRBP::Crypto do
2
2
  let(:account) { "rDbWJ9C7uExThZYAwV8m6LsZ5YSX3sa6US" }
3
- let(:expected) { [0x8a, 0x28, 0x1b, 0x5e, 0x46, 0xb0, 0x27, 0xc3, 0x70, 0x26, 0xe3, 0x8d, 0xbc, 0x5f, 0x9a, 0xa1, 0x4c, 0x37, 0x51, 0x45].pack("C*") }
3
+ let(:account_id) { 788735140293854337814932116604999410196843811141 }
4
+ let(:parsed) { [0x8a, 0x28, 0x1b, 0x5e, 0x46, 0xb0, 0x27, 0xc3, 0x70, 0x26, 0xe3, 0x8d, 0xbc, 0x5f, 0x9a, 0xa1, 0x4c, 0x37, 0x51, 0x45].pack("C*") }
4
5
 
5
6
  it "generates valid account" do
6
7
  acct = described_class.account
7
8
  expect(described_class.account?(acct[:account])).to be(true)
8
9
  end
9
10
 
11
+ it "returns account id for account" do
12
+ expect(described_class.account_id(account).to_bn).to eq(account_id)
13
+ end
14
+
10
15
  it "parses account" do
11
16
  a = described_class.parse_account(account)
12
17
  expect(a).to_not be_nil
13
- expect(a).to eq(expected)
18
+ expect(a).to eq(parsed)
14
19
  end
15
20
 
16
21
  it "does not parse invalid account" do
@@ -0,0 +1,11 @@
1
+ describe XRBP::NodeStore::Amendments do
2
+ describe "#fix1141?" do
3
+ context "specified time is > fix1141_time" do
4
+ it "returns true"
5
+ end
6
+
7
+ context "specified time is <= fix1141_time" do
8
+ it "returns false"
9
+ end
10
+ end
11
+ end
@@ -3,7 +3,70 @@ shared_examples "a database parser" do |opts={}|
3
3
  let(:ledger) { {"nt_ledger"=>1, "hp_ledger_master"=>"4c575200", "index"=>47508432, "total_coins"=>18248035087498961665, "parent_hash"=>"C4FAD6EB19F6A6089E7AEBC73AF596DB471305BC0EBB99D9D1414BD4DB8C9D43", "tx_hash"=>"934A5E57C598F0505664F9349DA87FD32AFBE1C315A2CB6CFB5B690AECC6B1A3", "account_hash"=>"FEA269DAAA66C820978AC95F0399ECACA7A3ABAC91402C9FC7961A53D053332F", "parent_close_time"=>Time.parse("2019-05-25T20:12:20Z").utc, "close_time"=>Time.parse("2019-05-25T20:12:21Z").utc, "close_time_resolution"=>10, "close_flags"=>0} }
4
4
 
5
5
  let(:tx_key) { ["003b960d5e30ff91a91f7c900509a99a8a6b89441b76158e5ed295ad1d27b0e7"].pack("H*") }
6
- let(:tx) { {:node=>{:transaction_type=>:payment, :flags=>2147942400, :sequence=>3366484, :last_ledger_sequence=>47713577, :amount=>XRBP::NodeStore::STAmount.new(:mantissa => 1000000000000000, :exponent => 86, :issue => XRBP::NodeStore::Issue.new("XCN", "rPFLkxQk6xUGdGYEykqe7PR25Gr7mLHDc8")), :fee=>XRBP::NodeStore::STAmount.new(:issue => XRBP::NodeStore.xrp_issue, :mantissa => 11), :send_max=>XRBP::NodeStore::STAmount.new(:issue => XRBP::NodeStore.xrp_issue, :mantissa => 10000000000), :signing_pub_key=>"030ac4f2ba6e1ff86beb234b639918dafdf0675032ae264d2b39641503822373fe", :txn_signature=>"304402207ac9c62a6e8a876e67945edebcd3f6c71c36c95fbbb0ce05a85871d0794b1b8c0220297fe7195beb083a78ff9de4d822b5e4cb42be7e5a8662bcbc2f42c737e264bf", :account=>"rKLpjpCoXgLQQYQyj13zgay73rsgmzNH13", :destination=>"rKLpjpCoXgLQQYQyj13zgay73rsgmzNH13", :paths=>[[{:currency=>"CNY", :issuer=>"rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y"}, {:currency=>"USD", :issuer=>"rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq"}, {:currency=>"\x00\x00\x00"}, {:currency=>"XCN", :issuer=>"rPFLkxQk6xUGdGYEykqe7PR25Gr7mLHDc8"}], [{:currency=>"USD", :issuer=>"rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq"}, {:currency=>"CNY", :issuer=>"rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y"}, {:currency=>"\x00\x00\x00"}, {:currency=>"XCN", :issuer=>"rPFLkxQk6xUGdGYEykqe7PR25Gr7mLHDc8"}], [{:currency=>"CNY", :issuer=>"rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y"}, {:currency=>"CNY", :issuer=>"razqQKzJRdB4UxFPWf5NEpEG3WMkmwgcXA"}, {:currency=>"\x00\x00\x00"}, {:currency=>"XCN", :issuer=>"rPFLkxQk6xUGdGYEykqe7PR25Gr7mLHDc8"}], [{:currency=>"CNY", :issuer=>"razqQKzJRdB4UxFPWf5NEpEG3WMkmwgcXA"}, {:currency=>"CNY", :issuer=>"rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y"}, {:currency=>"\x00\x00\x00"}, {:currency=>"XCN", :issuer=>"rPFLkxQk6xUGdGYEykqe7PR25Gr7mLHDc8"}], [{:currency=>"CNY", :issuer=>"rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y"}, {:currency=>"EUR", :issuer=>"rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq"}, {:currency=>"\x00\x00\x00"}, {:currency=>"XCN", :issuer=>"rPFLkxQk6xUGdGYEykqe7PR25Gr7mLHDc8"}], [{:currency=>"EUR", :issuer=>"rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq"}, {:currency=>"CNY", :issuer=>"rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y"}, {:currency=>"\x00\x00\x00"}, {:currency=>"XCN", :issuer=>"rPFLkxQk6xUGdGYEykqe7PR25Gr7mLHDc8"}]]}, :meta=>{:transaction_index=>30, :affected_nodes=>[{:ledger_entry_type=>:account_root, :previous_txn_lgr_seq=>47713575, :previous_txn_id=>"2e397e516ccde852e829d36eb7d0d195dd9a974a188e213ad256c3ae3c131e58", :ledger_index=>"792ba4e4659c27cf3b63f96b34f158748b081cf532f6746a1e3ebd07acba1a0e", :previous_fields=>{:sequence=>3366484, :balance=>XRBP::NodeStore::STAmount.new(:issue => XRBP::NodeStore.xrp_issue, :mantissa => 1920887273)}, :final_fields=>{:flags=>0, :sequence=>3366485, :owner_count=>5, :balance=>XRBP::NodeStore::STAmount.new(:issue => XRBP::NodeStore.xrp_issue, :mantissa => 1920887262), :account=>"rKLpjpCoXgLQQYQyj13zgay73rsgmzNH13"}}], :transaction_result=>128}, :index=>"5C28E293E88CCEEFBBAB3EF58C1F8C1C02A5194F42A42349597DDCBC86BFEB0D"} }
6
+ let(:tx) { {
7
+ :node=>{
8
+ :transaction_type=>:Payment,
9
+ :flags=>2147942400,
10
+ :sequence=>3366484,
11
+ :last_ledger_sequence=>47713577,
12
+ :amount=>XRBP::NodeStore::STAmount.new(:issue => XRBP::NodeStore::Issue.new("XCN", "rPFLkxQk6xUGdGYEykqe7PR25Gr7mLHDc8"), :mantissa => 1000000000000000, :exponent => -11),
13
+ :fee=>XRBP::NodeStore::STAmount.new(:issue => XRBP::NodeStore.xrp_issue, :mantissa => 11),
14
+ :send_max=>XRBP::NodeStore::STAmount.new(:issue => XRBP::NodeStore.xrp_issue, :mantissa => 10000000000),
15
+ :signing_pub_key=>"030ac4f2ba6e1ff86beb234b639918dafdf0675032ae264d2b39641503822373fe",
16
+ :txn_signature=>"304402207ac9c62a6e8a876e67945edebcd3f6c71c36c95fbbb0ce05a85871d0794b1b8c0220297fe7195beb083a78ff9de4d822b5e4cb42be7e5a8662bcbc2f42c737e264bf",
17
+ :account=>"rKLpjpCoXgLQQYQyj13zgay73rsgmzNH13",
18
+ :destination=>"rKLpjpCoXgLQQYQyj13zgay73rsgmzNH13",
19
+ :paths=>[[
20
+ {:currency=>"CNY", :issuer=>"rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y"},
21
+ {:currency=>"USD", :issuer=>"rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq"},
22
+ {:currency=>"\x00\x00\x00"},
23
+ {:currency=>"XCN", :issuer=>"rPFLkxQk6xUGdGYEykqe7PR25Gr7mLHDc8"}
24
+ ],[
25
+ {:currency=>"USD", :issuer=>"rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq"},
26
+ {:currency=>"CNY", :issuer=>"rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y"},
27
+ {:currency=>"\x00\x00\x00"},
28
+ {:currency=>"XCN", :issuer=>"rPFLkxQk6xUGdGYEykqe7PR25Gr7mLHDc8"}
29
+ ],[
30
+ {:currency=>"CNY", :issuer=>"rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y"},
31
+ {:currency=>"CNY", :issuer=>"razqQKzJRdB4UxFPWf5NEpEG3WMkmwgcXA"},
32
+ {:currency=>"\x00\x00\x00"},
33
+ {:currency=>"XCN", :issuer=>"rPFLkxQk6xUGdGYEykqe7PR25Gr7mLHDc8"}
34
+ ],[
35
+ {:currency=>"CNY", :issuer=>"razqQKzJRdB4UxFPWf5NEpEG3WMkmwgcXA"},
36
+ {:currency=>"CNY", :issuer=>"rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y"},
37
+ {:currency=>"\x00\x00\x00"},
38
+ {:currency=>"XCN", :issuer=>"rPFLkxQk6xUGdGYEykqe7PR25Gr7mLHDc8"}
39
+ ],[
40
+ {:currency=>"CNY", :issuer=>"rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y"},
41
+ {:currency=>"EUR", :issuer=>"rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq"},
42
+ {:currency=>"\x00\x00\x00"},
43
+ {:currency=>"XCN", :issuer=>"rPFLkxQk6xUGdGYEykqe7PR25Gr7mLHDc8"}
44
+ ],[
45
+ {:currency=>"EUR", :issuer=>"rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq"},
46
+ {:currency=>"CNY", :issuer=>"rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y"},
47
+ {:currency=>"\x00\x00\x00"},
48
+ {:currency=>"XCN", :issuer=>"rPFLkxQk6xUGdGYEykqe7PR25Gr7mLHDc8"}]]
49
+ },:meta=>{
50
+ :transaction_index=>30,
51
+ :affected_nodes=>[{
52
+ :ledger_entry_type=>:account_root,
53
+ :previous_txn_lgr_seq=>47713575,
54
+ :previous_txn_id=>"2e397e516ccde852e829d36eb7d0d195dd9a974a188e213ad256c3ae3c131e58",
55
+ :ledger_index=>"792ba4e4659c27cf3b63f96b34f158748b081cf532f6746a1e3ebd07acba1a0e",
56
+ :previous_fields=>{
57
+ :sequence=>3366484,
58
+ :balance=>XRBP::NodeStore::STAmount.new(:issue => XRBP::NodeStore.xrp_issue, :mantissa => 1920887273)
59
+ },:final_fields=>{
60
+ :flags=>0,
61
+ :sequence=>3366485,
62
+ :owner_count=>5,
63
+ :balance=>XRBP::NodeStore::STAmount.new(:issue => XRBP::NodeStore.xrp_issue, :mantissa => 1920887262),
64
+ :account=>"rKLpjpCoXgLQQYQyj13zgay73rsgmzNH13"
65
+ }
66
+ }],
67
+ :transaction_result=>:tecPATH_DRY},
68
+ :index=>"5C28E293E88CCEEFBBAB3EF58C1F8C1C02A5194F42A42349597DDCBC86BFEB0D"
69
+ } }
7
70
 
8
71
  let(:ledger_entry_key) { ["002989b414eff398027fce4045f643d68aca440aeea11518a950550ca02c18dd"].pack("H*") }
9
72
  let(:ledger_entry) { {:type=>:account_root, :index=>"9A34C6D1C46168ECE90EB867C78AB2BECE80FAF5D86D09082017E2C89BD68E56", :fields=>{:flags=>0, :sequence=>2, :previous_txn_lgr_seq=>45017187, :owner_count=>0, :previous_txn_id=>"3133839f2401cc9a719778f53319486d11e7ad7ec7891ac083e2aa7eb924516a", :balance=>XRBP::NodeStore::STAmount.new(:issue => XRBP::NodeStore.xrp_issue, :mantissa => 20000000), :account=>"r4HW4bomLvvzA22dACJwUq95ZRr8ZAcXXs"}} }