xrbp 0.2.2 → 0.2.3

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