New integration tests:

* New tests for autobridging and freeze
* Discrepancy detection tests
* Don't let Mocha suppress load time errors
This commit is contained in:
Nicholas Dudfield
2014-05-26 18:58:24 +07:00
committed by Nik Bougalis
parent 0848e348bb
commit e14c700c60
16 changed files with 6506 additions and 157 deletions

177
test/autobridge-test.coffee Normal file
View File

@@ -0,0 +1,177 @@
################################### REQUIRES ###################################
extend = require 'extend'
fs = require 'fs'
assert = require 'assert'
{
Amount
UInt160
Transaction
} = require 'ripple-lib'
testutils = require './testutils'
{
LedgerState
LedgerVerifier
TestAccount
} = require './ledger-state'
{
beast_configured
is_focused_test
pretty_json
server_setup_teardown
skip_or_only
str_ends_with
submit_for_final
} = require './batmans-belt'
#################################### CONFIG ####################################
config = testutils.init_config()
#################################### HELPERS ###################################
make_offer = (remote, account, pays, gets, flag_or_flags) ->
tx = remote.transaction()
tx.offer_create(account, pays, gets)
tx.set_flags(flag_or_flags) if flag_or_flags?
tx
dump_rpc_script = (ledger_state, test_decl) ->
lines = ledger_state.compile_to_rpc_commands()
# Realias etc ;)
# TODO
account = test_decl.offer[0]
[pays, gets, flags] = test_decl.offer[1..]
tx = new Transaction({secrets: {}})
tx.offer_create(account, pays, gets)
tx.set_flags(flags)
tx_json = tx.tx_json
# Account: account
# TransactionType: "OfferCreate"
# TakerPays: pays
# TakerGets: gets
lines += "\nbuild/rippled submit #{account} '#{JSON.stringify tx_json}'"
lines += "\nbuild/rippled ledger_accept\n"
fs.writeFileSync(__dirname + '/../manual-offer-test.sh', lines)
dump_aliased_ledger = (pre_or_post, ledger_state, done) ->
# TODO: generify to post/pre
ledger_state.remote.request_ledger 'validated', {full: true}, (e, m) ->
ledger_dump = ledger_state.pretty_json m.ledger.accountState
fn = __dirname + "/../manual-offer-test-#{pre_or_post}-ledger.json"
fs.writeFileSync(fn, ledger_dump)
done()
################################# TEST FACTORY #################################
make_offer_create_test = (get_context, test_name, test_decl) ->
'''
@get_context {Function}
a getter function, which gets the current context with the ripple-lib remote
etc attached
@test_name {String}
This function will create a `test` using @test_name based on @test_decl
@test_decl {Object}
@pre_ledger
@post_ledger
@offer
'''
test_func = skip_or_only test_name, test
focused_test = is_focused_test test_name
test_func test_name, (done) ->
context = get_context()
remote = context.remote
ledger_state = context.ledger
tx = make_offer(remote, test_decl.offer...)
submit_for_final tx, (m) ->
'assert transaction was successful'
assert.equal m.metadata.TransactionResult, 'tesSUCCESS'
context.ledger.verifier(test_decl.post_ledger).do_verify (errors) ->
this_done = ->
assert Object.keys(errors).length == 0,
"post_ledger errors:\n"+ pretty_json errors
done()
if focused_test
dump_aliased_ledger('post', ledger_state, this_done)
else
this_done()
test_func
ledger_state_setup = (get_context, decls) ->
setup (done) ->
[test_name, test_decl] = decls.shift()
context = get_context()
focused_test = is_focused_test test_name
context.ledger =
new LedgerState(test_decl.pre_ledger, assert, context.remote, config)
if focused_test
dump_rpc_script(context.ledger, test_decl)
context.ledger.setup(
# console.log
->, # noop logging function
->
context.ledger.verifier().do_verify (errors) ->
assert Object.keys(errors).length == 0,
"pre_ledger errors:\n"+ pretty_json errors
if focused_test
dump_aliased_ledger('pre', context.ledger, done)
else
done()
)
############################### TEST DECLARATIONS ##############################
try
offer_create_tests = require("./offer-tests-json")
# console.log offer_create_tests
# offer_create_tests = JSON.parse offer_tests_string
extend offer_create_tests, {}
catch e
console.log e
if beast_configured('RIPPLE_ENABLE_AUTOBRIDGING', '1')
suite_func = suite
else
suite_func = suite.skip
suite_func 'Offer Create Tests', ->
try
get_context = server_setup_teardown()
# tests = ([k,v] for k,v of offer_create_tests)
tests = []
only = false
for k,v of offer_create_tests
f = make_offer_create_test(get_context, k, v)
if not only and f == test.only
only = [[k, v]]
if not str_ends_with k, '_skip'
tests.push [k,v]
# f = make_offer_create_test(get_context, k, v) for [k,v] in tests
ledger_state_setup(get_context, if only then only else tests)
catch e
console.log e

106
test/batmans-belt.coffee Normal file
View File

@@ -0,0 +1,106 @@
################################### REQUIRES ###################################
fs = require 'fs'
testutils = require './testutils'
################################### REQUIRES ###################################
exports.pretty_json = (v) -> JSON.stringify(v, undefined, 2)
exports.beast_configured = beast_configured = (k, v) ->
'''
A very naive regex search in $repo_root/src/BeastConfig.h
@k the name of the macro
@v the value as a string expected
@return {Boolean} k's configured value, trimmed, is v
'''
beast_configured.buf ?= fs.readFileSync("#{__dirname}/../src/BeastConfig.h")
pattern = "^#define\\s+#{k}\\s+(.*?)$"
res = (new RegExp(pattern, 'm').exec(beast_configured.buf))
return false if res == null
actual = res[1].trim()
return v == actual
exports.server_setup_teardown = (options) ->
{setup_func, teardown_func, post_setup, server_opts} = options ? {}
context = null
setup_func ?= setup
teardown_func ?= teardown
setup_func (done) ->
context = @
testutils.build_setup(server_opts).call @, ->
if post_setup?
post_setup(context, done)
else
done()
teardown_func (done) ->
testutils.build_teardown().call context, done
# We turn a function to access the `context`, if we returned it now, it
# would be null (DUH ;)
-> context
exports.str_ends_with = ends_with = (s, suffix) ->
~s.indexOf(suffix, s.length - suffix.length)
exports.skip_or_only = (title, test_or_suite) ->
if ends_with title, '_only'
test_or_suite.only
else if ends_with title, '_skip'
test_or_suite.skip
else
test_or_suite
exports.is_focused_test = (test_name) -> ends_with test_name, '_only'
class BailError extends Error
constructor: (@message) ->
@message ?= "Failed test due to relying on prior failed tests"
exports.suite_test_bailer = () ->
bailed = false
bail = (e) -> bailed = true
suiteSetup 'suite_test_bailer', ->
process.on 'uncaughtException', bail
suiteTeardown 'suite_test_bailer', ->
process.removeListener 'uncaughtException', bail
wrapper = (test_func) ->
test = (title, fn) ->
wrapped = (done) ->
if not bailed
fn(done)
else
# We could do this, but it's just noisy
if process.env.BAIL_PASSES
done()
else
done(new BailError)
test_func title, wrapped
test.only = test_func.only
test.skip = test_func.skip
return test
wrapper(global.test)
exports.submit_for_final = (tx, done) ->
'''
This helper submits a transaction, and once it's proposed sends a ledger
accept so the transaction will finalize.
'''
tx.on 'proposed', -> tx.remote.ledger_accept()
tx.on 'final', (m) -> done(m)
tx.submit()

View File

@@ -32,6 +32,8 @@ exports.servers = {
'rpc_ip' : "0.0.0.0",
'rpc_port' : 5005,
'local_sequence' : true,
'trace' : false,
// 'trace' : true,
'local_fee' : true,
// 'validation_seed' : "shhDFVsmS2GSu5vUyZSPXYfj1r79h",
// 'validators' : "n9L8LZZCwsdXzKUN9zoVxs4YznYXZ9hEhsQZY7aVpxtFaSceiyDZ beta",

View File

@@ -68,7 +68,7 @@ The tests are written in a declarative style:
$ signifies an order book rather than account
------------------------------------------------------------------------------
Tests can be written in the 'path-tests.json' file in same directory # <--
Tests can be written in the 'path-tests-json.js' file in same directory # <--
------------------------------------------------------------------------------
"""
#################################### HELPERS ###################################
@@ -455,8 +455,10 @@ define_suites = (path_finding_cases) ->
A0 = (new TestAccount('A0')).address
assert A0 == 'rBmhuVAvi372AerwzwERGjhLjqkMmAwxX'
path_finding_cases_string = fs.readFileSync(__dirname + "/path-tests.json")
path_finding_cases = JSON.parse path_finding_cases_string
try
path_finding_cases = require('./path-tests-json')
catch e
console.log e
# You need two gateways, same currency. A market maker. A source that trusts one
# gateway and holds its currency, and a destination that trusts the other.

View File

@@ -0,0 +1,135 @@
################################### REQUIRES ###################################
assert = require 'assert'
async = require 'async'
{
Amount
Meta
} = require 'ripple-lib'
testutils = require './testutils'
{
pretty_json
server_setup_teardown
} = require './batmans-belt'
#################################### CONFIG ####################################
config = testutils.init_config()
#################################### HELPERS ###################################
tx_blob_submit_factory = (remote, txn) ->
(next) ->
req = remote.request_submit()
req.message.tx_blob = txn
req.once 'error', -> assert false, "unexpected error submitting txns"
req.once 'success', (m) ->
remote.once 'ledger_closed', -> next()
remote.ledger_accept()
req.request()
offer_amounts = (fields) ->
[Amount.from_json(fields.TakerPays),
Amount.from_json(fields.TakerGets)]
executed_offers = (meta) ->
offers = {}
meta.nodes.forEach (n, i) ->
if n.entryType == 'Offer'
[prev_pays, prev_gets] = offer_amounts(n.fieldsPrev)
[final_pays, final_gets] = offer_amounts(n.fields)
summary=
pays_executed: prev_pays.subtract(final_pays).to_text_full()
gets_executed: prev_gets.subtract(final_gets).to_text_full()
pays_final: final_pays.to_text_full()
gets_final: final_gets.to_text_full()
owner: n.fields.Account
offers[n.ledgerIndex] = summary
offers
build_tx_blob_submission_series = (remote, txns_to_submit) ->
series = []
while (txn = txns_to_submit.shift())
series.push tx_blob_submit_factory remote, txn
series
compute_xrp_discrepancy = (fee, meta) ->
before = Amount.from_json(0)
after = Amount.from_json(fee)
meta.nodes.forEach (n, i) ->
if n.entryType == 'AccountRoot'
prev = n.fieldsPrev?.Balance || n.fields.Balance
final = n.fieldsFinal?.Balance || n.fields.Balance
before = before.add(Amount.from_json(prev))
after = after.add(Amount.from_json(final))
before.subtract(after)
suite 'Discrepancy test', ->
suite 'XRP Discrepancy', ->
get_context = server_setup_teardown({server_opts: {ledger_file: 'ledger-6226713.json'}})
test '01030E435C2EEBE2689FED7494BC159A4C9B98D0DF0B23F7DFCE223822237E8C', (done) ->
{remote} = get_context()
txns_to_submit = [
# This is the nasty one ;)
'1200002200020000240000124E61D5438D7EA4C680000000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD468400000000000000A69D4D3E7809B4814C8000000000000000000000000434E59000000000041C8BE2C0A6AA17471B9F6D0AF92AAB1C94D5A25732103FC5F96EA61889691EC7A56FB2B859B600DE68C0255BF580D5C22D02EB97AFCE474473045022100D14B60BC6E01E5C19471F87EB00A4BFCA16D039BB91AEE12DA1142E8C4CAE7C2022020E2809CF24DE2BC0C3DCF1A07C469DB415F880485B2B323E5B5AA1D9F6F22D48114AFD96601692A6C6416DBA294F0DA684675A824B28314AFD96601692A6C6416DBA294F0DA684675A824B20112300000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD401E5C92828261DBAAC933B6309C6F5C72AF020AFD4FF100000000000000000000000000000000000000000300000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD401E5C92828261DBAAC933B6309C6F5C72AF020AFD4FF01A034782E2DBAC4FB82B601CD50421E8EF24F3A00100000000000000000000000000000000000000000300000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD401E5C92828261DBAAC933B6309C6F5C72AF020AFD400'
]
series = build_tx_blob_submission_series remote, txns_to_submit
async.series series, ->
hash = '01030E435C2EEBE2689FED7494BC159A4C9B98D0DF0B23F7DFCE223822237E8C'
remote.request_tx hash, (e, m) ->
meta = new Meta(m.meta)
zero = Amount.from_json(0)
discrepancy = compute_xrp_discrepancy(m.Fee, meta)
assert discrepancy.equals(zero), discrepancy.to_text_full()
done()
suite 'RIPD 304', ->
get_context = server_setup_teardown({server_opts: {ledger_file: 'ledger-7145315.json'}})
test 'B1A305038D43BCDF3EA1D096E6A0ACC5FB0ECAE0C8F5D3A54AD76A2AA1E20EC4', (done) ->
{remote} = get_context()
txns_to_submit = [
'120008228000000024000030FD2019000030F0201B006D076B68400000000000000F732103325EB29A014DDE22289D0EA989861D481D54D54C727578AB6C2F18BC342D3829744630440220668D06C44144C284E0346EE7785EB41B72EDBB244FE6EE02F317011A07023C63022067A52367AC01941A3FE19477D7F588C862704A44A8A771BCAD6B7A9119B71E9E8114A7C1C74DADB3693C199888A901FC2B7FD0884EE1'
'1200002200020000240000163161D551C37937E080000000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD468400000000000000A69D4D0BEC6A319514D00000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D173210342D49ADE3DBC5E8E25F02B4FBB169448157B016BA203A268C3E8CCC9DF1AE39F74463044022069A2B0F79A042CC65C7CCFDF610DEAD8FDA12F53E43061F9F75FAD5B398E657A02200A4A45BB4F31E922A52F843D5CE96D83446992A13393871C31FCD8A52AE4329F81148C4BE4DBAA81F7BC66720E5874EBD2D90C9563EA83148C4BE4DBAA81F7BC66720E5874EBD2D90C9563EA0112100000000000000000000000000000000000000000300000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD401E5C92828261DBAAC933B6309C6F5C72AF020AFD4FF300000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD401E5C92828261DBAAC933B6309C6F5C72AF020AFD4FF01585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C101DD39C650A96EDA48334E70CC4A85B8B2E8502CD3300000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD401E5C92828261DBAAC933B6309C6F5C72AF020AFD400'
'1200082200000000240019B1A520190019B1A46840000000000000327321025718736160FA6632F48EA4354A35AB0340F8D7DC7083799B9C57C3E937D7185174463044022048B3669EDCA795A1897DA3C7328C8526940708DBB3FFAD88CA5DC22D0398A67502206B37796A743105DE05EE1A11BE017404B4F41FA17E6449E390C36F69D8907C078114AFFDCC86D33C153DA185156EB32608ACCF0BC713'
'1200072200000000240019B1A664D550AF2D90A009D80000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD46540000002540BE4006840000000000000327321025718736160FA6632F48EA4354A35AB0340F8D7DC7083799B9C57C3E937D71851744630440220509F09B609573BC8ADDD55449DBD5201A40F6C1C3AA2D5D984ACB54E0F651F2E022019E6AF2937A5E76D8C9A2B5B0C4704D6BE637AAC17F2EE135DA449B0892B728B8114AFFDCC86D33C153DA185156EB32608ACCF0BC713'
# This is the nasty one ;)
'1200002200020000240000163261D551C37937E080000000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD468400000000000000A69D4D0BEC6A319514D00000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D173210342D49ADE3DBC5E8E25F02B4FBB169448157B016BA203A268C3E8CCC9DF1AE39F74473045022100C79C86BD18BBBC0343F0EB268A7770FDAEC30748ECCB9A6483E2B11488749DC00220544A5FF2D085FA5DD2A003AA9C3F031B8FE3FD4A443B659B9EE84E165795BC0581148C4BE4DBAA81F7BC66720E5874EBD2D90C9563EA83148C4BE4DBAA81F7BC66720E5874EBD2D90C9563EA0112100000000000000000000000000000000000000000300000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD401E5C92828261DBAAC933B6309C6F5C72AF020AFD4FF300000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD401E5C92828261DBAAC933B6309C6F5C72AF020AFD4FF01585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C101DD39C650A96EDA48334E70CC4A85B8B2E8502CD3300000000000000000000000004A50590000000000E5C92828261DBAAC933B6309C6F5C72AF020AFD401E5C92828261DBAAC933B6309C6F5C72AF020AFD400'
]
series = build_tx_blob_submission_series remote, txns_to_submit
async.series series, ->
hash = 'B1A305038D43BCDF3EA1D096E6A0ACC5FB0ECAE0C8F5D3A54AD76A2AA1E20EC4'
remote.request_tx hash, (e, m) ->
meta = new Meta(m.meta)
expected = {
"003313896DA56CFA0996B36AF066589EF0E689230E67DA01D13320289C834A93": {
"pays_executed": "955.967853/XRP",
"gets_executed": "445.6722130686/JPY/rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"pays_final": "245,418.978522/XRP",
"gets_final": "114414.3277869564/JPY/rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"owner": "rQLEvSnbgpcUXkTU8VvSzUPX4scukCjuzb"
},
"9847793D6B936812907ED58455FBA4195205ABCACBE28DF9584C3A195A221E59": {
"pays_executed": "4.19284145965/USD/rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"gets_executed": "955.967853/XRP",
"pays_final": "13630.84998220238/USD/rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"gets_final": "3,107,833.795934/XRP",
"owner": "rEhKZcz5Ndjm9BzZmmKrtvhXPnSWByssDv"
}
}
## rhsxr2aAddyCKx5iZctebT4Padxv6iWDxb
assert.deepEqual executed_offers(meta), expected
done()

3866
test/fixtures/ledger-6226713.json vendored Normal file

File diff suppressed because it is too large Load Diff

644
test/fixtures/ledger-7145315.json vendored Normal file
View File

@@ -0,0 +1,644 @@
{
"accepted": true,
"accountState": [
{
"Account": "rQLEvSnbgpcUXkTU8VvSzUPX4scukCjuzb",
"BookDirectory": "92466F5377C34C5EA957034339321E217A23FA4E27A31D475B079EDE3AE532A1",
"BookNode": "0000000000000000",
"Flags": 0,
"LedgerEntryType": "Offer",
"OwnerNode": "0000000000000000",
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
"PreviousTxnLgrSeq": 7145315,
"Sequence": 710,
"TakerGets": {
"currency": "JPY",
"issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"value": "119860.00000002"
},
"TakerPays": "257099957100",
"index": "003313896DA56CFA0996B36AF066589EF0E689230E67DA01D13320289C834A93"
},
{
"Account": "rEhKZcz5Ndjm9BzZmmKrtvhXPnSWByssDv",
"Balance": "93495113081301",
"Flags": 0,
"LedgerEntryType": "AccountRoot",
"OwnerCount": 7,
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
"PreviousTxnLgrSeq": 7145315,
"Sequence": 422,
"index": "0522B5ABDFEBEF28C47AC64AEBCB6B5ABE0A00388141E32E177052B9742D47CF"
},
{
"Flags": 0,
"Indexes": [
"89B369C951B15067BD52EF3F5FEC540DE773C4B6718FE1342222C1C935ED42DF",
"9847793D6B936812907ED58455FBA4195205ABCACBE28DF9584C3A195A221E59"
],
"LedgerEntryType": "DirectoryNode",
"Owner": "rEhKZcz5Ndjm9BzZmmKrtvhXPnSWByssDv",
"RootIndex": "07E25CB74E530EF7B1D6718EE48BAC672B31F7E1908EC77D5C0DA82633A7919C",
"index": "07E25CB74E530EF7B1D6718EE48BAC672B31F7E1908EC77D5C0DA82633A7919C"
},
{
"Flags": 0,
"Indexes": [
"707CEC6ABA7BDA95DF712DABD6BC3BB1DD69CEED25977AB643EA94A471DD842C",
"003313896DA56CFA0996B36AF066589EF0E689230E67DA01D13320289C834A93"
],
"LedgerEntryType": "DirectoryNode",
"Owner": "rQLEvSnbgpcUXkTU8VvSzUPX4scukCjuzb",
"RootIndex": "19477D184C2E34D30057C81581E4F04DDE2B64542D2CE7F1145B3D4A207E9301",
"index": "19477D184C2E34D30057C81581E4F04DDE2B64542D2CE7F1145B3D4A207E9301"
},
{
"Account": "rHsZHqa5oMQNL5hFm4kfLd47aEMYjPstpg",
"BookDirectory": "BCF012C63E83DAF510C7B6B27FE1045CF913B0CF94049AB04E10B058B79947A8",
"BookNode": "0000000000000000",
"Flags": 0,
"LedgerEntryType": "Offer",
"OwnerNode": "0000000000000000",
"PreviousTxnID": "E5EC82E300777C4C0AE24499E2DA175AE00065BE8AC7C91518C37876377EC473",
"PreviousTxnLgrSeq": 7145315,
"Sequence": 1683876,
"TakerGets": "10000000000",
"TakerPays": {
"currency": "JPY",
"issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"value": "4697.494711257"
},
"index": "19D60E66D4CAB32F0823C5EAA8A58284AD48BAB63CACCBECB76166A990EECECF"
},
{
"Balance": {
"currency": "USD",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "-0.0001000001918501896"
},
"Flags": 131072,
"HighLimit": {
"currency": "USD",
"issuer": "rhS6Pb8oBMKshN6EznMeWCHJNHJuoom63r",
"value": "0"
},
"HighNode": "0000000000000000",
"LedgerEntryType": "RippleState",
"LowLimit": {
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"value": "0"
},
"LowNode": "0000000000000000",
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
"PreviousTxnLgrSeq": 7145315,
"index": "19DA8116FACDDB48DBFAEEB7036394883D42C67CA23349E3582AC63A61C0C08F"
},
{
"ExchangeRate": "52221700304BE774",
"Flags": 0,
"Indexes": ["E986149D0F53C5F05FCF13751CA959F9BA31FABC0766AE2A4E906C02F10A5F28"],
"LedgerEntryType": "DirectoryNode",
"RootIndex": "1ACB79E7B8B4C59269CEAC5CA907D5E8C3BF3B294A33D3D752221700304BE774",
"TakerGetsCurrency": "0000000000000000000000004A50590000000000",
"TakerGetsIssuer": "E5C92828261DBAAC933B6309C6F5C72AF020AFD4",
"TakerPaysCurrency": "0000000000000000000000005553440000000000",
"TakerPaysIssuer": "0A20B3C85F482532A9578DBB3950B85CA06594D1",
"index": "1ACB79E7B8B4C59269CEAC5CA907D5E8C3BF3B294A33D3D752221700304BE774"
},
{
"Balance": {
"currency": "JPY",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "28771.88711726011"
},
"Flags": 1114112,
"HighLimit": {
"currency": "JPY",
"issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"value": "0"
},
"HighNode": "0000000000000000",
"LedgerEntryType": "RippleState",
"LowLimit": {
"currency": "JPY",
"issuer": "rhS6Pb8oBMKshN6EznMeWCHJNHJuoom63r",
"value": "10000000"
},
"LowNode": "0000000000000000",
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
"PreviousTxnLgrSeq": 7145315,
"index": "47E2DF7CEC8979AFEB877402F1A8A4A514CE6F0159F5BDCA3538BD4A244EE81E"
},
{
"Flags": 0,
"Indexes": [
"8782F28AC73A79162357EB1FB38E0AA5F55C066F0F2ACC774BBF095B21E07E64",
"E232591F55AA7B82F584A5DBE414CA67C15869B0936C875095B5D08810A99EA5",
"7D1F4F82D74930ED8F0CD4D75CD9B41E0A1ED7CAA6508AEF6C13BE57FCE01220",
"B8F0B4940547D30B9706F32D3EB3A8EC60FD5F09499A157BB05549514DB335BC"
],
"LedgerEntryType": "DirectoryNode",
"Owner": "rhsxr2aAddyCKx5iZctebT4Padxv6iWDxb",
"RootIndex": "49DA34D0CCDB7AF9A1B5751ECDC647D6379033B0126D645CD16395E302239BAE",
"index": "49DA34D0CCDB7AF9A1B5751ECDC647D6379033B0126D645CD16395E302239BAE"
},
{
"Balance": {
"currency": "JPY",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "692595.1141695322"
},
"Flags": 1114112,
"HighLimit": {
"currency": "JPY",
"issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"value": "0"
},
"HighNode": "0000000000000000",
"LedgerEntryType": "RippleState",
"LowLimit": {
"currency": "JPY",
"issuer": "rD8FXhcHtG8m1iHTJogoahPx3LJRjT7tpa",
"value": "10000000"
},
"LowNode": "0000000000000000",
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
"PreviousTxnLgrSeq": 7145315,
"index": "4D8AD84A38E3B4EE756E7B64CA539DF51FDEF036D96E8427FB461266B55CE97E"
},
{
"Account": "rD8FXhcHtG8m1iHTJogoahPx3LJRjT7tpa",
"Balance": "4049943048",
"Flags": 0,
"LedgerEntryType": "AccountRoot",
"OwnerCount": 5,
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
"PreviousTxnLgrSeq": 7145315,
"Sequence": 5681,
"index": "582F150193E8572D608ACED9E978EF85139834CDB24882550F74560D49F15040"
},
{
"Flags": 0,
"Indexes": ["E232591F55AA7B82F584A5DBE414CA67C15869B0936C875095B5D08810A99EA5"],
"LedgerEntryType": "DirectoryNode",
"Owner": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"RootIndex": "6319526CE8F9A8A44D7A870A89DC1B4AD848AA4F066FCB5390A9A268F6E16AEA",
"index": "6319526CE8F9A8A44D7A870A89DC1B4AD848AA4F066FCB5390A9A268F6E16AEA"
},
{
"Account": "rQLEvSnbgpcUXkTU8VvSzUPX4scukCjuzb",
"Balance": "1244946044233",
"Flags": 0,
"LedgerEntryType": "AccountRoot",
"OwnerCount": 10,
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
"PreviousTxnLgrSeq": 7145315,
"Sequence": 711,
"index": "6A240B2366203BFB4CA5A14D78E43DA9EF5E6F1AF66B186842FF80876AEA9055"
},
{
"Balance": {
"currency": "JPY",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "0"
},
"Flags": 1114112,
"HighLimit": {
"currency": "JPY",
"issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"value": "0"
},
"HighNode": "0000000000000000",
"LedgerEntryType": "RippleState",
"LowLimit": {
"currency": "JPY",
"issuer": "rHsZHqa5oMQNL5hFm4kfLd47aEMYjPstpg",
"value": "1"
},
"LowNode": "0000000000000000",
"PreviousTxnID": "7A2C8EADCC58C5E7927FBD9C98C010292757282994E751BE1D162C65E6219658",
"PreviousTxnLgrSeq": 7145307,
"index": "6CB0EB288A835747D5FAE5FD036BAE11DB4A4787DE283199BF60C5B69291F418"
},
{
"Balance": {
"currency": "JPY",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "-160129.7932997147"
},
"Flags": 131072,
"HighLimit": {
"currency": "JPY",
"issuer": "rQLEvSnbgpcUXkTU8VvSzUPX4scukCjuzb",
"value": "100000000"
},
"HighNode": "0000000000000000",
"LedgerEntryType": "RippleState",
"LowLimit": {
"currency": "JPY",
"issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"value": "0"
},
"LowNode": "0000000000000000",
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
"PreviousTxnLgrSeq": 7145315,
"index": "707CEC6ABA7BDA95DF712DABD6BC3BB1DD69CEED25977AB643EA94A471DD842C"
},
{
"Account": "rhsxr2aAddyCKx5iZctebT4Padxv6iWDxb",
"BookDirectory": "FF304540EB391AD26231FC5FC98ECF6E85A41DE173C1FCE052227E0BAF9B1166",
"BookNode": "0000000000000000",
"Flags": 0,
"LedgerEntryType": "Offer",
"OwnerNode": "0000000000000000",
"PreviousTxnID": "4C1104ADBE498DED6F146B82C57E5DFC062C265354E86C0DF7155515F636DCF9",
"PreviousTxnLgrSeq": 7141869,
"Sequence": 573,
"TakerGets": {
"currency": "JPY",
"issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"value": "3089.919693423499"
},
"TakerPays": {
"currency": "USD",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"value": "29.99922032449999"
},
"index": "7D1F4F82D74930ED8F0CD4D75CD9B41E0A1ED7CAA6508AEF6C13BE57FCE01220"
},
{
"Flags": 0,
"Indexes": [
"19DA8116FACDDB48DBFAEEB7036394883D42C67CA23349E3582AC63A61C0C08F",
"E5186A5ED55BFE053D7F7553693A3AE3288933241CA32AEC8C136BF1B2B3238B",
"89B369C951B15067BD52EF3F5FEC540DE773C4B6718FE1342222C1C935ED42DF"
],
"LedgerEntryType": "DirectoryNode",
"Owner": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"RootIndex": "7E1247F78EFC74FA9C0AE39F37AF433966615EB9B757D8397C068C2849A8F4A5",
"index": "7E1247F78EFC74FA9C0AE39F37AF433966615EB9B757D8397C068C2849A8F4A5"
},
{
"Account": "rhsxr2aAddyCKx5iZctebT4Padxv6iWDxb",
"Balance": "141999116",
"Flags": 0,
"LedgerEntryType": "AccountRoot",
"OwnerCount": 14,
"PreviousTxnID": "52C20FF8946459677CC0B30E2F2D5E9452D22221ADFBD35E9C3192DA7AAD6F82",
"PreviousTxnLgrSeq": 7145047,
"Sequence": 582,
"index": "7E7EBE111CB117C19F55CB87A1166D3235D32605AD29F5EFF795D84962FE4D5A"
},
{
"Flags": 0,
"Indexes": [
"E5186A5ED55BFE053D7F7553693A3AE3288933241CA32AEC8C136BF1B2B3238B",
"4D8AD84A38E3B4EE756E7B64CA539DF51FDEF036D96E8427FB461266B55CE97E"
],
"LedgerEntryType": "DirectoryNode",
"Owner": "rD8FXhcHtG8m1iHTJogoahPx3LJRjT7tpa",
"RootIndex": "803DB604B91165DFB9BEAE165415AE68D9E2D1C7C4BDC3AB569F6F3D4FB7D2EB",
"index": "803DB604B91165DFB9BEAE165415AE68D9E2D1C7C4BDC3AB569F6F3D4FB7D2EB"
},
{
"Balance": {
"currency": "JPY",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "9975.85778757913"
},
"Flags": 1114112,
"HighLimit": {
"currency": "JPY",
"issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"value": "0"
},
"HighNode": "0000000000000000",
"LedgerEntryType": "RippleState",
"LowLimit": {
"currency": "JPY",
"issuer": "rhsxr2aAddyCKx5iZctebT4Padxv6iWDxb",
"value": "1000000"
},
"LowNode": "0000000000000000",
"PreviousTxnID": "52C20FF8946459677CC0B30E2F2D5E9452D22221ADFBD35E9C3192DA7AAD6F82",
"PreviousTxnLgrSeq": 7145047,
"index": "8782F28AC73A79162357EB1FB38E0AA5F55C066F0F2ACC774BBF095B21E07E64"
},
{
"Account": "rhS6Pb8oBMKshN6EznMeWCHJNHJuoom63r",
"Balance": "277438248",
"Flags": 0,
"LedgerEntryType": "AccountRoot",
"OwnerCount": 10,
"PreviousTxnID": "9E793DBDA4D9C663C3D3FC57EBF99C3DA14FE092FCEB585E06FA0DF08123CD88",
"PreviousTxnLgrSeq": 7145267,
"Sequence": 12441,
"index": "87F612640CBC200BAFC0D02A8F484F2E7EA5A06836A443BF1BFF9DE686659FB3"
},
{
"Balance": {
"currency": "USD",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "-37608.51473797789"
},
"Flags": 2228224,
"HighLimit": {
"currency": "USD",
"issuer": "rEhKZcz5Ndjm9BzZmmKrtvhXPnSWByssDv",
"value": "50000"
},
"HighNode": "0000000000000000",
"LedgerEntryType": "RippleState",
"LowLimit": {
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"value": "0"
},
"LowNode": "0000000000000000",
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
"PreviousTxnLgrSeq": 7145315,
"index": "89B369C951B15067BD52EF3F5FEC540DE773C4B6718FE1342222C1C935ED42DF"
},
{
"ExchangeRate": "5B079EDE3AE532A1",
"Flags": 0,
"Indexes": ["003313896DA56CFA0996B36AF066589EF0E689230E67DA01D13320289C834A93"],
"LedgerEntryType": "DirectoryNode",
"RootIndex": "92466F5377C34C5EA957034339321E217A23FA4E27A31D475B079EDE3AE532A1",
"TakerGetsCurrency": "0000000000000000000000004A50590000000000",
"TakerGetsIssuer": "E5C92828261DBAAC933B6309C6F5C72AF020AFD4",
"TakerPaysCurrency": "0000000000000000000000000000000000000000",
"TakerPaysIssuer": "0000000000000000000000000000000000000000",
"index": "92466F5377C34C5EA957034339321E217A23FA4E27A31D475B079EDE3AE532A1"
},
{
"Account": "rEhKZcz5Ndjm9BzZmmKrtvhXPnSWByssDv",
"BookDirectory": "DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4C0F95030898047E",
"BookNode": "0000000000000000",
"Flags": 0,
"LedgerEntryType": "Offer",
"OwnerNode": "0000000000000000",
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
"PreviousTxnLgrSeq": 7145315,
"Sequence": 413,
"TakerGets": "3119514774512",
"TakerPays": {
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"value": "13682.08234438571"
},
"index": "9847793D6B936812907ED58455FBA4195205ABCACBE28DF9584C3A195A221E59"
},
{
"Account": "rGJrzrNBfv6ndJmzt1hTUJVx7z8o2bg3of",
"Balance": "20136213182",
"Flags": 0,
"LedgerEntryType": "AccountRoot",
"OwnerCount": 14,
"PreviousTxnID": "9C83F32CEE14164F5E74B92FB18B5F401E0707D1D0B89E3B0F76EE9B22205465",
"PreviousTxnLgrSeq": 7145315,
"Sequence": 12541,
"index": "9A3D8BCEE8B1A6812356F2D15767A72F4AB2F4117A5316F17BFDE6AFF3EDAD14"
},
{
"Flags": 0,
"Indexes": [
"47E2DF7CEC8979AFEB877402F1A8A4A514CE6F0159F5BDCA3538BD4A244EE81E",
"19DA8116FACDDB48DBFAEEB7036394883D42C67CA23349E3582AC63A61C0C08F",
"E986149D0F53C5F05FCF13751CA959F9BA31FABC0766AE2A4E906C02F10A5F28"
],
"LedgerEntryType": "DirectoryNode",
"Owner": "rhS6Pb8oBMKshN6EznMeWCHJNHJuoom63r",
"RootIndex": "9D0414606FD28AB1AED055D29F79DFF44F5B1AC33D2A0CBD6A236BEAB0B7F0D6",
"index": "9D0414606FD28AB1AED055D29F79DFF44F5B1AC33D2A0CBD6A236BEAB0B7F0D6"
},
{
"Account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"Balance": "14474614581",
"Domain": "736E6170737761702E7573",
"Flags": 655360,
"LedgerEntryType": "AccountRoot",
"OwnerCount": 0,
"PreviousTxnID": "85F810B93488EE16BAFDB7FD590E447DA9ECC4BF3F4DAD11B52A820D7B65A952",
"PreviousTxnLgrSeq": 7145095,
"Sequence": 50,
"index": "A3C1529122C3DBD6C96B9DF009FF4896023FE6B4E05A508B1E81F3DCD9A6274B"
},
{
"Account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"Balance": "276876380",
"Flags": 0,
"LedgerEntryType": "AccountRoot",
"OwnerCount": 1,
"PreviousTxnID": "2B46B3B775C536E77B15B82086FA42904C01414E4C78164F82038ADBCF352621",
"PreviousTxnLgrSeq": 7131717,
"Sequence": 503,
"index": "A5F37C05FBED611F326E48E6F0D14C6BBAC664CE14ACF4FCC0E959FD60330716"
},
{
"Flags": 0,
"Indexes": [
"6CB0EB288A835747D5FAE5FD036BAE11DB4A4787DE283199BF60C5B69291F418",
"19D60E66D4CAB32F0823C5EAA8A58284AD48BAB63CACCBECB76166A990EECECF"
],
"LedgerEntryType": "DirectoryNode",
"Owner": "rHsZHqa5oMQNL5hFm4kfLd47aEMYjPstpg",
"RootIndex": "B65B458CE90B410C20AC46F87480569F661F3AB426FF773B2EEB4E75947A5FD6",
"index": "B65B458CE90B410C20AC46F87480569F661F3AB426FF773B2EEB4E75947A5FD6"
},
{
"Account": "rHsZHqa5oMQNL5hFm4kfLd47aEMYjPstpg",
"Balance": "100108450682",
"Flags": 0,
"LedgerEntryType": "AccountRoot",
"OwnerCount": 32,
"PreviousTxnID": "E5EC82E300777C4C0AE24499E2DA175AE00065BE8AC7C91518C37876377EC473",
"PreviousTxnLgrSeq": 7145315,
"Sequence": 1683877,
"index": "B79D156918390AC41C4DDE5F181417D55B01D0D183ACBB1B892FF163C5BC8344"
},
{
"Account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"Balance": "4382136375728",
"Domain": "6269747374616D702E6E6574",
"EmailHash": "5B33B93C7FFE384D53450FC666BB11FB",
"Flags": 131072,
"LedgerEntryType": "AccountRoot",
"OwnerCount": 0,
"PreviousTxnID": "D7EDB85038C0EAEDB2FA174074FBB741A002B87735DDD2133971A9D3E67101CC",
"PreviousTxnLgrSeq": 7145290,
"Sequence": 542,
"TransferRate": 1002000000,
"index": "B7D526FDDF9E3B3F95C3DC97C353065B0482302500BBB8051A5C090B596C6133"
},
{
"Account": "rhsxr2aAddyCKx5iZctebT4Padxv6iWDxb",
"BookDirectory": "FF304540EB391AD26231FC5FC98ECF6E85A41DE173C1FCE05222D49D5E80FAFB",
"BookNode": "0000000000000000",
"Flags": 0,
"LedgerEntryType": "Offer",
"OwnerNode": "0000000000000000",
"PreviousTxnID": "7ADF6C4A72AF9E0DE7C11B2A5A336F47E91EFC5A201EE635D56E79D1E58A3C44",
"PreviousTxnLgrSeq": 7133996,
"Sequence": 572,
"TakerGets": {
"currency": "JPY",
"issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"value": "3060"
},
"TakerPays": {
"currency": "USD",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"value": "30"
},
"index": "B8F0B4940547D30B9706F32D3EB3A8EC60FD5F09499A157BB05549514DB335BC"
},
{
"ExchangeRate": "4E10B058B79947A8",
"Flags": 0,
"Indexes": ["19D60E66D4CAB32F0823C5EAA8A58284AD48BAB63CACCBECB76166A990EECECF"],
"LedgerEntryType": "DirectoryNode",
"RootIndex": "BCF012C63E83DAF510C7B6B27FE1045CF913B0CF94049AB04E10B058B79947A8",
"TakerGetsCurrency": "0000000000000000000000000000000000000000",
"TakerGetsIssuer": "0000000000000000000000000000000000000000",
"TakerPaysCurrency": "0000000000000000000000004A50590000000000",
"TakerPaysIssuer": "E5C92828261DBAAC933B6309C6F5C72AF020AFD4",
"index": "BCF012C63E83DAF510C7B6B27FE1045CF913B0CF94049AB04E10B058B79947A8"
},
{
"Flags": 0,
"Indexes": [
"6CB0EB288A835747D5FAE5FD036BAE11DB4A4787DE283199BF60C5B69291F418",
"4D8AD84A38E3B4EE756E7B64CA539DF51FDEF036D96E8427FB461266B55CE97E",
"47E2DF7CEC8979AFEB877402F1A8A4A514CE6F0159F5BDCA3538BD4A244EE81E",
"8782F28AC73A79162357EB1FB38E0AA5F55C066F0F2ACC774BBF095B21E07E64",
"707CEC6ABA7BDA95DF712DABD6BC3BB1DD69CEED25977AB643EA94A471DD842C"
],
"LedgerEntryType": "DirectoryNode",
"Owner": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"RootIndex": "DD8763F37822A3129919DA194DC31D9A9FA5BEA547E233B32E4573F0E60D46D3",
"index": "DD8763F37822A3129919DA194DC31D9A9FA5BEA547E233B32E4573F0E60D46D3"
},
{
"ExchangeRate": "4C0F95030898047E",
"Flags": 0,
"Indexes": ["9847793D6B936812907ED58455FBA4195205ABCACBE28DF9584C3A195A221E59"],
"LedgerEntryType": "DirectoryNode",
"RootIndex": "DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4C0F95030898047E",
"TakerGetsCurrency": "0000000000000000000000000000000000000000",
"TakerGetsIssuer": "0000000000000000000000000000000000000000",
"TakerPaysCurrency": "0000000000000000000000005553440000000000",
"TakerPaysIssuer": "0A20B3C85F482532A9578DBB3950B85CA06594D1",
"index": "DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4C0F95030898047E"
},
{
"Balance": {
"currency": "USD",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "66.41236909484335"
},
"Flags": 65536,
"HighLimit": {
"currency": "USD",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"value": "0"
},
"HighNode": "0000000000000000",
"LedgerEntryType": "RippleState",
"LowLimit": {
"currency": "USD",
"issuer": "rhsxr2aAddyCKx5iZctebT4Padxv6iWDxb",
"value": "0"
},
"LowNode": "0000000000000000",
"PreviousTxnID": "9BDD9E9FCB197AE5AB5C77F57C18C89F777AB499EF87125C8233011D227181D8",
"PreviousTxnLgrSeq": 7143372,
"index": "E232591F55AA7B82F584A5DBE414CA67C15869B0936C875095B5D08810A99EA5"
},
{
"Balance": {
"currency": "USD",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "-51.33482690775639"
},
"Flags": 2228224,
"HighLimit": {
"currency": "USD",
"issuer": "rD8FXhcHtG8m1iHTJogoahPx3LJRjT7tpa",
"value": "100000"
},
"HighNode": "0000000000000000",
"LedgerEntryType": "RippleState",
"LowLimit": {
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"value": "0"
},
"LowNode": "0000000000000000",
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
"PreviousTxnLgrSeq": 7145315,
"index": "E5186A5ED55BFE053D7F7553693A3AE3288933241CA32AEC8C136BF1B2B3238B"
},
{
"Account": "rhS6Pb8oBMKshN6EznMeWCHJNHJuoom63r",
"BookDirectory": "1ACB79E7B8B4C59269CEAC5CA907D5E8C3BF3B294A33D3D752221700304BE774",
"BookNode": "0000000000000000",
"Flags": 0,
"LedgerEntryType": "Offer",
"OwnerNode": "0000000000000000",
"PreviousTxnID": "8C20D6F490017E225B1541A8BC138A45F96D0868CE67CEFFD500B5DA2390D76E",
"PreviousTxnLgrSeq": 7145315,
"Sequence": 12437,
"TakerGets": {
"currency": "JPY",
"issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"value": "3965.070032365735"
},
"TakerPays": {
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"value": "38.04658677730045"
},
"index": "E986149D0F53C5F05FCF13751CA959F9BA31FABC0766AE2A4E906C02F10A5F28"
},
{
"ExchangeRate": "52227E0BAF9B1166",
"Flags": 0,
"Indexes": ["7D1F4F82D74930ED8F0CD4D75CD9B41E0A1ED7CAA6508AEF6C13BE57FCE01220"],
"LedgerEntryType": "DirectoryNode",
"RootIndex": "FF304540EB391AD26231FC5FC98ECF6E85A41DE173C1FCE052227E0BAF9B1166",
"TakerGetsCurrency": "0000000000000000000000004A50590000000000",
"TakerGetsIssuer": "E5C92828261DBAAC933B6309C6F5C72AF020AFD4",
"TakerPaysCurrency": "0000000000000000000000005553440000000000",
"TakerPaysIssuer": "DD39C650A96EDA48334E70CC4A85B8B2E8502CD3",
"index": "FF304540EB391AD26231FC5FC98ECF6E85A41DE173C1FCE052227E0BAF9B1166"
},
{
"ExchangeRate": "5222D49D5E80FAFB",
"Flags": 0,
"Indexes": ["B8F0B4940547D30B9706F32D3EB3A8EC60FD5F09499A157BB05549514DB335BC"],
"LedgerEntryType": "DirectoryNode",
"RootIndex": "FF304540EB391AD26231FC5FC98ECF6E85A41DE173C1FCE05222D49D5E80FAFB",
"TakerGetsCurrency": "0000000000000000000000004A50590000000000",
"TakerGetsIssuer": "E5C92828261DBAAC933B6309C6F5C72AF020AFD4",
"TakerPaysCurrency": "0000000000000000000000005553440000000000",
"TakerPaysIssuer": "DD39C650A96EDA48334E70CC4A85B8B2E8502CD3",
"index": "FF304540EB391AD26231FC5FC98ECF6E85A41DE173C1FCE05222D49D5E80FAFB"
}
],
"account_hash": "21520663FE5BBAF001849ECB598D8784A73081F7CBDD4A96614FAB5D438DB0AA",
"close_time": 455849230,
"close_time_human": "2014-Jun-12 00:47:10",
"close_time_resolution": 10,
"closed": true,
"hash": "9CB34D86A7C18EE551B9961C771BA94785F57B5CE88676365B9CFD4F36787CC2",
"ledger_hash": "9CB34D86A7C18EE551B9961C771BA94785F57B5CE88676365B9CFD4F36787CC2",
"ledger_index": "7145315",
"parent_hash": "22B5D2A2264612E7AD3D9A58D1CDD9442EFC28ABFB92FE2363B2F1B623909526",
"seqNum": "7145315",
"totalCoins": "99999989148483436",
"total_coins": "99999989148483436",
"transaction_hash": "EAAD60D178E8D2CC428C147EF39407AF2D073D518A7E64788EC29F321C875C5A",
"transactions": []
}

813
test/freeze-test.coffee Normal file
View File

@@ -0,0 +1,813 @@
################################### REQUIRES ###################################
extend = require 'extend'
fs = require 'fs'
assert = require 'assert'
async = require 'async'
{
Remote
UInt160
Transaction
Amount
} = require 'ripple-lib'
testutils = require './testutils'
{
LedgerState
LedgerVerifier
TestAccount
} = require './ledger-state'
{
pretty_json
server_setup_teardown
skip_or_only
submit_for_final
suite_test_bailer
} = require './batmans-belt'
################################ FREEZE OVERVIEW ###############################
'''
Freeze Feature Overview
=======================
A frozen line prevents funds from being transferred to anyone except back to the
issuer, yet does not prohibit acquisition of more of the issuer's assets, via
receipt of a Payment or placing offers to buy them.
A trust line's Flags now assigns two bits, for toggling the freeze status of
each side of a trustline.
GlobalFreeze
------------
There is also, a global freeze, toggled by a bit in the AccountRoot Flags, which
freezes ALL trust lines for an account.
Offers can not be created to buy or sell assets issued by an account with
GlobalFreeze set.
Use cases (via (David (JoelKatz) Schwartz)):
There are two basic cases. One is a case where some kind of bug or flaw causes
a large amount of an asset to somehow be created and the gateway hasn't yet
decided how it's going to handle it.
The other is a reissue where one asset is replaced by another. In a community
credit case, say someone tricks you into issuing a huge amount of an asset,
but you set the no freeze flag. You can still set global freeze to protect
others from trading valuable assets for assets you issued that are now,
unfortunately, worthless. And if you're an honest guy, you can set up a new
account and re-issue to people who are legitimate holders
NoFreeze
--------
NoFreeze, is a set only flag bit in the account root.
When this bit is set:
An account may not freeze it's side of a trustline
The NoFreeze bit can not be cleared
The GlobalFreeze flag bit can not cleared
GlobalFreeze can be used as a matter of last resort
Flag Definitions
================
LedgerEntry flags
-----------------
RippleState
LowFreeze 0x00400000
HighFreeze 0x00800000
AccountRoot
NoFreeze 0x00200000
GlobalFeeze 0x00400000
Transaction flags
-----------------
TrustSet (used with Flags)
SetFreeze 0x00100000
ClearFreeze 0x00200000
AccountSet (used with SetFlag/ClearFlag)
NoFreeze 6
GlobalFreeze 7
API Implications
================
transaction.Payment
-------------------
Any offers containing frozen funds found in the process of a tesSUCCESS will be
removed from the books.
transaction.OfferCreate
-----------------------
Selling an asset from a globally frozen issuer fails with tecFROZEN
Selling an asset from a frozen line fails with tecUNFUNDED_OFFER
Any offers containing frozen funds found in the process of a tesSUCCESS will be
removed from the books.
request.book_offers
-------------------
All offers selling assets from a frozen line/acount (offers created before a
freeze) will be filtered, except where in a global freeze situation where:
TakerGets.issuer == Account ($frozen_account)
request.path_find & transaction.Payment
---------------------------------------
No Path may contain frozen trustlines, or offers (placed, prior to freezing) of
assets from frozen lines.
request.account_offers
-----------------------
These offers are unfiltered, merely walking the owner directory and reporting
all offers.
'''
################################################################################
Flags =
sle:
AccountRoot:
PasswordSpent: 0x00010000
RequireDestTag: 0x00020000
RequireAuth: 0x00040000
DisallowXRP: 0x00080000
NoFreeze: 0x00200000
GlobalFreeze: 0x00400000
RippleState:
LowFreeze: 0x00400000
HighFreeze: 0x00800000
tx:
SetFlag:
AccountRoot:
NoFreeze: 6
GlobalFreeze: 7
TrustSet:
# New Flags
SetFreeze: 0x00100000
ClearFreeze: 0x00200000
Transaction.flags.TrustSet ||= {};
# Monkey Patch SetFreeze and ClearFreeze into old version of ripple-lib
Transaction.flags.TrustSet.SetFreeze = Flags.tx.TrustSet.SetFreeze
Transaction.flags.TrustSet.ClearFreeze = Flags.tx.TrustSet.ClearFreeze
GlobalFreeze = Flags.tx.SetFlag.AccountRoot.GlobalFreeze
NoFreeze = Flags.tx.SetFlag.AccountRoot.NoFreeze
#################################### CONFIG ####################################
config = testutils.init_config()
#################################### HELPERS ###################################
get_lines = (remote, acc, done) ->
remote.request_account_lines acc, null, 'validated', (err, lines) ->
done(lines)
account_set_factory = (remote, ledger, alias_for) ->
(acc, fields, done) ->
tx = remote.transaction()
tx.account_set(acc)
extend tx.tx_json, fields
tx.on 'error', (err) ->
assert !err, ("Unexpected error #{ledger.pretty_json err}\n" +
"don't use this helper if expecting an error")
submit_for_final tx, (m) ->
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
affected_root = m.metadata.AffectedNodes[0].ModifiedNode
assert.equal alias_for(affected_root.FinalFields.Account), acc
done(affected_root)
make_payment_factory = (remote, ledger) ->
(src, dst, amount, path, on_final) ->
if typeof path == 'function'
on_final = path
path = undefined
src_account = UInt160.json_rewrite src
dst_account = UInt160.json_rewrite dst
dst_amount = Amount.from_json amount
tx = remote.transaction().payment(src_account, dst_account, dst_amount)
if not path?
tx.build_path(true)
else
tx.path_add path.path
tx.send_max path.send_max
tx.on 'error', (err) ->
if err.engine_result?.slice(0,3) == 'tec'
# We can handle this in the `final`
return
assert !err, ("Unexpected error #{ledger.pretty_json err}\n" +
"don't use this helper if expecting an error")
submit_for_final tx, (m) ->
on_final m
create_offer_factory = (remote, ledger) ->
(acc, pays, gets, on_final) ->
tx = remote.transaction().offer_create(acc, pays, gets)
tx.on 'error', (err) ->
if err.engine_result?.slice(0,3) == 'tec'
# We can handle this in the `final`
return
assert !err, ("Unexpected error #{ledger.pretty_json err}\n" +
"don't use this helper if expecting an error")
submit_for_final tx, (m) ->
on_final m
ledger_state_setup = (pre_ledger) ->
post_setup = (context, done) ->
context.ledger = new LedgerState(pre_ledger,
assert,
context.remote,
config)
context.ledger.setup(
#-> # noop logging function
->
->
context.ledger.verifier().do_verify (errors) ->
assert Object.keys(errors).length == 0,
"pre_ledger errors:\n"+ pretty_json errors
done()
)
verify_ledger_state = (ledger, remote, pre_state, done) ->
{config, assert, am} = ledger
verifier = new LedgerVerifier(pre_state, remote, config, assert, am)
verifier.do_verify (errors) ->
assert Object.keys(errors).length == 0,
"ledger_state errors:\n"+ pretty_json errors
done()
book_offers_factory = (remote) ->
(pays, gets, on_book) ->
asset = (a) ->
if typeof a == 'string'
ret = {}
[ret['currency'], ret['issuer']] = a.split('/')
ret
else
a
book=
pays: asset(pays)
gets: asset(gets)
remote.request_book_offers book, null, null, (err, book) ->
if err
assert !err, "error with request_book_offers #{err}"
on_book(book)
suite_setup = (state) ->
'''
@state
The ledger state to setup, after starting the server
'''
opts =
setup_func: suiteSetup
teardown_func: suiteTeardown
post_setup: ledger_state_setup(state)
get_context = server_setup_teardown(opts)
helpers = null
helpers_factory = ->
context = {ledger, remote} = get_context()
alog = (obj) -> console.log ledger.pretty_json obj
lines_for = (acc) -> get_lines(remote, arguments...)
alias_for = (acc) -> ledger.am.lookup_alias(acc)
verify_ledger_state_before_suite = (pre) ->
suiteSetup (done) -> verify_ledger_state(ledger, remote, pre, done)
{
context: context
remote: remote
ledger: ledger
lines_for: lines_for
alog: alog
alias_for: alias_for
book_offers: book_offers_factory(remote)
create_offer: create_offer_factory(remote, ledger, alias_for)
account_set: account_set_factory(remote, ledger, alias_for)
make_payment: make_payment_factory(remote, ledger, alias_for)
verify_ledger_state_before_suite: verify_ledger_state_before_suite
}
get_helpers = -> (helpers = helpers ? helpers_factory())
{
get_context: get_context
get_helpers: get_helpers
}
##################################### TESTS ####################################
execute_if_enabled = (fn) ->
path = "#{__dirname}/../src/ripple/module/data/protocol/TxFlags.h"
if /asfGlobalFreeze/.exec(fs.readFileSync(path)) == null
suite = global.suite.skip
fn(suite)
execute_if_enabled (suite) ->
suite 'Freeze Feature', ->
suite 'RippleState Freeze', ->
test = suite_test_bailer()
h = null
{get_helpers} = suite_setup
accounts:
G1: balance: ['1000.0']
bob:
balance: ['1000.0', '10-100/USD/G1']
alice:
balance: ['1000.0', '100/USD/G1']
offers: [['500.0', '100/USD/G1']]
suite 'Account with line unfrozen (proving operations normally work)', ->
test 'can make Payment on that line', (done) ->
{remote} = h = get_helpers()
h.make_payment 'alice', 'bob', '1/USD/G1', (m) ->
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
done()
test 'can receive Payment on that line', (done) ->
h.make_payment 'bob', 'alice', '1/USD/G1', (m) ->
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
done()
suite 'Is created via a TrustSet with SetFreeze flag', ->
test 'sets LowFreeze | HighFreeze flags', (done) ->
{remote} = h
tx = remote.transaction()
tx.ripple_line_set('G1', '0/USD/bob')
tx.set_flags('SetFreeze')
submit_for_final tx, (m) ->
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
affected = m.metadata.AffectedNodes
ripple_state = affected[1].ModifiedNode
final = ripple_state.FinalFields
assert.equal h.alias_for(final.LowLimit.issuer), 'G1'
assert final.Flags & Flags.sle.RippleState.LowFreeze
assert !(final.Flags & Flags.sle.RippleState.HighFreeze)
done()
suite 'Account with line frozen by issuer', ->
test 'can buy more assets on that line', (done) ->
h.create_offer 'bob', '5/USD/G1', '25.0', (m) ->
meta = m.metadata
assert.equal meta.TransactionResult, 'tesSUCCESS'
line = meta.AffectedNodes[3]['ModifiedNode'].FinalFields
assert.equal h.alias_for(line.HighLimit.issuer), 'bob'
assert.equal line.Balance.value, '-15' # HighLimit means balance inverted
done()
test 'can not sell assets from that line', (done) ->
h.create_offer 'bob', '1.0', '5/USD/G1', (m) ->
assert.equal m.engine_result, 'tecUNFUNDED_OFFER'
done()
test 'can receive Payment on that line', (done) ->
h.make_payment 'alice', 'bob', '1/USD/G1', (m) ->
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
done()
test 'can not make Payment from that line', (done) ->
h.make_payment 'bob', 'alice', '1/USD/G1', (m) ->
assert.equal m.engine_result, 'tecPATH_DRY'
done()
suite 'request_account_lines', ->
test 'shows `freeze_peer` and `freeze` respectively', (done) ->
async.parallel [
(next) ->
h.lines_for 'G1', (lines) ->
for line in lines.lines
if h.alias_for(line.account) == 'bob'
assert.equal line.freeze, true
assert.equal line.balance, '-16'
# unless we get here, the test will hang alerting us to
# something amiss
next() # setImmediate ;)
break
(next) ->
h.lines_for 'bob', (lines) ->
for line in lines.lines
if h.alias_for(line.account) == 'G1'
assert.equal line.freeze_peer, true
assert.equal line.balance, '16'
next()
break
], ->
done()
suite 'Is cleared via a TrustSet with ClearFreeze flag', ->
test 'sets LowFreeze | HighFreeze flags', (done) ->
{remote} = h
tx = remote.transaction()
tx.ripple_line_set('G1', '0/USD/bob')
tx.set_flags('ClearFreeze')
submit_for_final tx, (m) ->
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
affected = m.metadata.AffectedNodes
ripple_state = affected[1].ModifiedNode
final = ripple_state.FinalFields
assert.equal h.alias_for(final.LowLimit.issuer), 'G1'
assert !(final.Flags & Flags.sle.RippleState.LowFreeze)
assert !(final.Flags & Flags.sle.RippleState.HighFreeze)
done()
suite 'Global (AccountRoot) Freeze', ->
# NoFreeze: 0x00200000
# GlobalFreeze: 0x00400000
test = suite_test_bailer()
h = null
{get_helpers} = suite_setup
accounts:
G1:
balance: ['12000.0']
offers: [['10000.0', '100/USD/G1'], ['100/USD/G1', '10000.0']]
A1:
balance: ['1000.0', '1000/USD/G1']
offers: [['10000.0', '100/USD/G1']]
trusts: ['1200/USD/G1']
A2:
balance: ['20000.0', '100/USD/G1']
trusts: ['200/USD/G1']
offers: [['100/USD/G1', '10000.0']]
A3:
balance: ['20000.0', '100/BTC/G1']
A4:
balance: ['20000.0', '100/BTC/G1']
suite 'Is toggled via AccountSet using SetFlag and ClearFlag', ->
test 'SetFlag GlobalFreeze should set 0x00400000 in Flags', (done) ->
{remote} = h = get_helpers()
h.account_set 'G1', SetFlag: GlobalFreeze, (root) ->
new_flags = root.FinalFields.Flags
assert !(new_flags & Flags.sle.AccountRoot.NoFreeze)
assert (new_flags & Flags.sle.AccountRoot.GlobalFreeze)
done()
test 'ClearFlag GlobalFreeze should clear 0x00400000 in Flags', (done) ->
{remote} = h = get_helpers()
h.account_set 'G1', ClearFlag: GlobalFreeze, (root) ->
new_flags = root.FinalFields.Flags
assert !(new_flags & Flags.sle.AccountRoot.NoFreeze)
assert !(new_flags & Flags.sle.AccountRoot.GlobalFreeze)
done()
suite 'Account without GlobalFreeze (proving operations normally work)', ->
suite 'have visible offers', ->
test 'where taker_gets is $unfrozen_issuer', (done) ->
{remote} = h = get_helpers()
h.book_offers 'XRP', 'USD/G1', (book) ->
assert.equal book.offers.length, 2
aliases = (h.alias_for(o.Account) for o in book.offers).sort()
assert.equal aliases[0], 'A1'
assert.equal aliases[1], 'G1'
done()
test 'where taker_pays is $unfrozen_issuer', (done) ->
h.book_offers 'USD/G1', 'XRP', (book) ->
assert.equal book.offers.length, 2
aliases = (h.alias_for(o.Account) for o in book.offers).sort()
assert.equal aliases[0], 'A2'
assert.equal aliases[1], 'G1'
done()
suite 'it\'s assets can be', ->
test 'bought on the market', (next) ->
h.create_offer 'A3', '1/BTC/G1', '1.0', (m) ->
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
next()
test 'sold on the market', (next) ->
h.create_offer 'A4', '1.0', '1/BTC/G1', (m) ->
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
next()
suite 'Payments', ->
test 'direct issues can be sent', (done) ->
{remote} = h = get_helpers()
h.make_payment 'G1', 'A2', '1/USD/G1', (m) ->
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
done()
test 'direct redemptions can be sent', (done) ->
h.make_payment 'A2', 'G1', '1/USD/G1', (m) ->
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
done()
test 'via rippling can be sent', (done) ->
h.make_payment 'A2', 'A1', '1/USD/G1', (m) ->
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
done()
test 'via rippling can be sent back', (done) ->
h.make_payment 'A2', 'A1', '1/USD/G1', (m) ->
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
done()
suite 'Account with GlobalFreeze', ->
suite 'Needs to set GlobalFreeze first', ->
test 'SetFlag GlobalFreeze will toggle back to freeze', (done) ->
h.account_set 'G1', SetFlag: GlobalFreeze, (root) ->
new_flags = root.FinalFields.Flags
assert !(new_flags & Flags.sle.AccountRoot.NoFreeze)
assert (new_flags & Flags.sle.AccountRoot.GlobalFreeze)
done()
suite 'it\'s assets can\'t be', ->
test 'bought on the market', (next) ->
h.create_offer 'A3', '1/BTC/G1', '1.0', (m) ->
assert.equal m.engine_result, 'tecFROZEN'
next()
test 'sold on the market', (next) ->
h.create_offer 'A4', '1.0', '1/BTC/G1', (m) ->
assert.equal m.engine_result, 'tecFROZEN'
next()
suite 'it\'s offers are filtered', ->
test ':TODO:verify: books_offers(*, $frozen_account/*) shows offers '+
'owned by $frozen_account ', (done) ->
h.book_offers 'XRP', 'USD/G1', (book) ->
assert.equal book.offers.length, 1
done()
test ':TODO:verify: books_offers($frozen_account/*, *) shows no offers', (done) ->
h.book_offers 'USD/G1', 'XRP', (book) ->
assert.equal book.offers.length, 0
done()
test 'account_offers always shows their own offers', (done) ->
{remote} = h = get_helpers()
remote.request_account_offers 'G1', null, 'validated', (err, res) ->
assert.equal res.offers.length, 2
done()
suite 'Payments', ->
test 'direct issues can be sent', (done) ->
{remote} = h = get_helpers()
h.make_payment 'G1', 'A2', '1/USD/G1', (m) ->
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
done()
test 'direct redemptions can be sent', (done) ->
h.make_payment 'A2', 'G1', '1/USD/G1', (m) ->
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
done()
test 'via rippling cant be sent', (done) ->
h.make_payment 'A2', 'A1', '1/USD/G1', (m) ->
assert.equal m.engine_result, 'tecPATH_DRY'
done()
suite 'Accounts with NoFreeze', ->
test = suite_test_bailer()
h = null
{get_helpers} = suite_setup
accounts:
G1: balance: ['12000.0']
A1: balance: ['1000.0', '1000/USD/G1']
suite 'TrustSet NoFreeze', ->
test 'should set 0x00200000 in Flags', (done) ->
h = get_helpers()
h.account_set 'G1', SetFlag: NoFreeze, (root) ->
new_flags = root.FinalFields.Flags
assert (new_flags & Flags.sle.AccountRoot.NoFreeze)
assert !(new_flags & Flags.sle.AccountRoot.GlobalFreeze)
done()
test 'can not be cleared', (done) ->
h.account_set 'G1', ClearFlag: NoFreeze, (root) ->
new_flags = root.FinalFields.Flags
assert (new_flags & Flags.sle.AccountRoot.NoFreeze)
assert !(new_flags & Flags.sle.AccountRoot.GlobalFreeze)
done()
suite 'GlobalFreeze', ->
test 'can set GlobalFreeze', (done) ->
h.account_set 'G1', SetFlag: GlobalFreeze, (root) ->
new_flags = root.FinalFields.Flags
assert (new_flags & Flags.sle.AccountRoot.NoFreeze)
assert (new_flags & Flags.sle.AccountRoot.GlobalFreeze)
done()
test 'can not unset GlobalFreeze', (done) ->
h.account_set 'G1', ClearFlag: GlobalFreeze, (root) ->
new_flags = root.FinalFields.Flags
assert (new_flags & Flags.sle.AccountRoot.NoFreeze)
assert (new_flags & Flags.sle.AccountRoot.GlobalFreeze)
done()
suite 'their trustlines', ->
test 'can\'t be frozen', (done) ->
{remote} = h = get_helpers()
tx = remote.transaction()
tx.ripple_line_set('G1', '0/USD/A1')
tx.set_flags('SetFreeze')
submit_for_final tx, (m) ->
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
affected = m.metadata.AffectedNodes
assert.equal affected.length, 1
affected_type = affected[0]['ModifiedNode'].LedgerEntryType
assert.equal affected_type, 'AccountRoot'
done()
suite 'Offers for frozen trustlines (not GlobalFreeze)', ->
test = suite_test_bailer()
remote = h = null
{get_helpers} = suite_setup
accounts:
G1:
balance: ['1000.0']
A2:
balance: ['2000.0']
trusts: ['1000/USD/G1']
A3:
balance: ['1000.0', '2000/USD/G1']
offers: [['1000.0', '1000/USD/G1']]
A4:
balance: ['1000.0', '2000/USD/G1']
suite 'will be removed by Payment with tesSUCCESS', ->
test 'can normally make a payment partially consuming offer', (done) ->
{remote} = h = get_helpers()
path =
path: [{"currency": "USD", "issuer": "G1"}]
send_max: '1.0'
h.make_payment 'A2', 'G1', '1/USD/G1', path, (m) ->
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
done()
test 'offer was only partially consumed', (done) ->
remote.request_account_offers 'A3', null, 'validated', (err, res) ->
assert res.offers.length == 1
assert res.offers[0].taker_gets.value, '999'
done()
test 'someone else creates an offer providing liquidity', (done) ->
h.create_offer 'A4', '999.0', '999/USD/G1', (m) ->
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
affected = m.metadata.AffectedNodes
done()
test 'owner of partially consumed offer\'s line is frozen', (done) ->
tx = remote.transaction()
tx.ripple_line_set('G1', '0/USD/A3')
tx.set_flags('SetFreeze')
submit_for_final tx, (m) ->
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
affected = m.metadata.AffectedNodes
ripple_state = affected[1].ModifiedNode
final = ripple_state.FinalFields
assert.equal h.alias_for(final.HighLimit.issuer), 'G1'
assert !(final.Flags & Flags.sle.RippleState.LowFreeze)
assert (final.Flags & Flags.sle.RippleState.HighFreeze)
done()
test 'Can make a payment via the new offer', (done) ->
path =
path: [{"currency": "USD", "issuer": "G1"}]
send_max: '1.0'
h.make_payment 'A2', 'G1', '1/USD/G1', path, (m) ->
# assert.equal m.engine_result, 'tecPATH_PARTIAL' # tecPATH_DRY
assert.equal m.metadata.TransactionResult, 'tesSUCCESS' # tecPATH_DRY
done()
test 'Partially consumed offer was removed by tes* payment', (done) ->
remote.request_account_offers 'A3', null, 'validated', (err, res) ->
assert res.offers.length == 0
done()
suite 'will be removed by OfferCreate with tesSUCCESS', ->
test 'freeze the new offer', (done) ->
tx = remote.transaction()
tx.ripple_line_set('G1', '0/USD/A4')
tx.set_flags('SetFreeze')
submit_for_final tx, (m) ->
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
affected = m.metadata.AffectedNodes
ripple_state = affected[0].ModifiedNode
final = ripple_state.FinalFields
assert.equal h.alias_for(final.LowLimit.issuer), 'G1'
assert (final.Flags & Flags.sle.RippleState.LowFreeze)
assert !(final.Flags & Flags.sle.RippleState.HighFreeze)
done()
test 'can no longer create a crossing offer', (done) ->
h.create_offer 'A2', '999/USD/G1', '999.0', (m) ->
assert.equal m.metadata?.TransactionResult, 'tesSUCCESS'
affected = m.metadata.AffectedNodes
created = affected[5].CreatedNode
new_fields = created.NewFields
assert.equal h.alias_for(new_fields.Account), 'A2'
done()
test 'offer was removed by offer_create', (done) ->
remote.request_account_offers 'A4', null, 'validated', (err, res) ->
assert res.offers.length == 0
done()

View File

@@ -0,0 +1,152 @@
################################################################################
async = require 'async'
simple_assert = require 'assert'
deep_eq = require 'deep-equal'
testutils = require './testutils'
{
LedgerVerifier
Balance
} = require './ledger-state'
#################################### CONFIG ####################################
config = testutils.init_config()
#################################### HELPERS ###################################
assert = simple_assert
prettyj = pretty_json = (v) -> JSON.stringify(v, undefined, 2)
describe 'Balance', ->
it 'parses native balances', ->
bal = new Balance("1.000")
assert.equal bal.is_native, true
assert.equal bal.limit, null
it 'parses iou balances', ->
bal = new Balance("1.000/USD/bob")
assert.equal bal.is_native, false
assert.equal bal.limit, null
assert.equal bal.amount.currency().to_json(), 'USD'
it 'parses iou balances with limits', ->
bal = new Balance("1-500/USD/bob")
assert.equal bal.is_native, false
assert.equal bal.amount.currency().to_json(), 'USD'
assert.equal bal.limit.to_json().value, '500'
assert.equal bal.amount.to_json().value, '1'
describe 'LedgerVerifier', ->
lv = null
declaration=
accounts:
bob:
balance: ['100.0', '200-500/USD/alice']
offers: [['89.0', '100/USD/alice'], ['89.0', '100/USD/alice']]
# We are using this because mocha and coffee-script is a retarded combination
# unfortunately, which terminates the program silently upon any require time
# exceptions. TODO: investigate obviously, but for the moment this is an
# acceptable workaround.
suiteSetup ->
remote_dummy = {set_secret: (->)}
lv = new LedgerVerifier(declaration, remote_dummy, config, assert)
it 'tracks xrp balances', ->
assert.equal lv.xrp_balances['bob'].to_json(), '100000000'
it 'tracks iou balances', ->
assert.equal lv.iou_balances['bob']['USD/alice'].to_json().value, '200'
it 'tracks iou trust limits', ->
assert.equal lv.trusts['bob']['USD/alice'].to_json().value, '500'
it 'can verify', ->
account_offers = [
{
"account": "bob",
"offers": [
{
"flags": 65536,
"seq": 2,
"taker_gets": {
"currency": "USD",
"issuer": "alice",
"value": "100"
},
"taker_pays": "88000000"
}
]
}
]
account_lines = [{
"account": "bob",
"lines": [
{
"account": "alice",
"balance": "201",
"currency": "USD",
"limit": "500",
"limit_peer": "0",
"quality_in": 0,
"quality_out": 0
},
]
}]
account_infos = [{
"account_data": {
"Account": "bob",
"Balance": "999"+ "999"+ "970",
"Flags": 0,
"LedgerEntryType": "AccountRoot",
"OwnerCount": 0,
"PreviousTxnID": "3D7823B577A5AF5860273B3DD13CA82D072B63B3B095DE1784604A5B41D7DD1D",
"PreviousTxnLgrSeq": 5,
"Sequence": 3,
"index": "59BEA57D1A27B6A560ECA226ABD10DE80C3ADC6961039908087ACDFA92F71489"
},
"ledger_current_index": 8
}]
errors = lv.verify account_infos, account_lines, account_offers
assert.equal errors.bob.balance['USD/alice'].expected, '200'
assert.equal errors.bob.balance['USD/alice'].actual, '201'
assert.equal errors.bob.balance['XRP'].expected, '100'
assert.equal errors.bob.balance['XRP'].actual, '999.99997'
assert.equal errors.bob.offers[0].taker_pays.actual, '88/XRP'
assert.equal errors.bob.offers[0].taker_pays.expected, '89/XRP'
# {"expected":["89.0","100/USD/alice"],"actual":"missing"}
assert.equal errors.bob.offers[1].actual, 'missing'
expected = {
"bob": {
"balance": {
"XRP": {
"actual": "999.99997",
"expected": "100"
},
"USD/alice": {
"actual": "201",
"expected": "200"
}
},
"offers": [
{
"taker_pays": {
"expected": "89/XRP",
"actual": "88/XRP"
}
},
"missing"
]
}
}

View File

@@ -9,10 +9,10 @@ assert = require 'assert'
Remote
Seed
Base
UInt160
Transaction
sjcl
} = require 'ripple-lib'
{Server} = require './server'
testutils = require './testutils'
#################################### HELPERS ###################################
@@ -36,10 +36,167 @@ exports.TestAccount = class TestAccount
@passphrase = passphrase
[@address, @master_seed, @key_pair] = @derive_pair(passphrase)
############################# LEDGER STATE COMPILER ############################
parse_balance_and_trust = (val) ->
reg = new RegExp("([0-9.]+)-([0-9.]+)(/[^/]+/[^/]+)")
m = reg.exec val
if m != null
[m[1] + m[3], m[2] + m[3]]
else
undefined
exports.LedgerState = class LedgerState
setup_issuer_realiaser: ->
exports.parse_amount = parse_amount = (amt_val) ->
amt = Amount.from_json(amt_val)
if not amt.is_valid()
amt = Amount.from_human(amt_val)
if not amt.is_valid()
amt = null
amt
exports.Balance = class Balance
'''
Represents a parsed balance declaration, which could represent an xrp balance
or an iou balance and optional limit.
@amount
@limit
@balance
'''
constructor: (value) ->
limited = parse_balance_and_trust value
if limited?
[amount, limit] = limited
@amount = parse_amount amount
@limit = parse_amount limit
@is_native = false
else
@amount = parse_amount value
@is_native = @amount.is_native()
@limit = null
################################################################################
class BulkRequests
constructor: (@remote, @assert, @pretty_json) ->
transactor: (fn, args_list, on_each, callback) ->
if args_list.length == 0
return callback()
if not callback?
callback = on_each
on_each = null
@assert callback?, "Must supply a callback"
finalized = {
n: args_list.length
one: ->
if --finalized.n <= 0
callback()
}
#remote = @remote
async.concatSeries(args_list, ((args, callback) =>
tx = @remote.transaction()
fn.apply(tx, args)
on_each?(args..., tx) # after payment() offer_create() etc so set_flags works
tx.on("proposed", (m) =>
@assert m.engine_result is "tesSUCCESS", "Transactor failure: #{@pretty_json m}"
callback()
# testutils.ledger_close remote, ->
).on('final', (m) =>
finalized.one()
# callback()
)
.on("error", (m) =>
@assert false, @pretty_json m
).submit()
),
=> testutils.ledger_close @remote, ->
)
requester: (fn, args_list, on_each, callback, on_results) ->
if not callback?
callback = on_each
on_each = null
@assert callback?, "Must supply a callback"
async.concatSeries(args_list, ((args, callback) =>
req = fn.apply @remote, args
on_each?(args..., req)
req.on("success", (m) =>
if m.status?
@assert m.status is "success", "requester failure: #{@pretty_json m}"
callback(null, m)
).on("error", (m) =>
@assert false, @pretty_json m
).request()
),
(error, results_list) ->
on_results?(results_list)
callback(error, results_list)
)
################################# ALIAS MANAGER ################################
class AliasManager
constructor: (@config, remote, aliases) ->
'''
@config
includes `accounts` property, with structure same as that exported
in testconfig.js
@remote
a Remote object
@aliases
A list of aliases
'''
@add_accounts_to_config(@config, aliases)
@set_test_account_secrets(remote, @config)
@realias_issuer = @create_issuer_realiaser()
@alias_lookup = @create_alias_lookup()
create_alias_lookup: ->
lookup = {}
for nick,acc of @config.accounts
lookup[acc.account] = nick
lookup
lookup_alias: (address) ->
@alias_lookup[UInt160.json_rewrite address]
pretty_json: (v) =>
@realias_issuer pretty_json(v)
add_accounts_to_config: (config, accounts) ->
for account in accounts
if not config.accounts[account]?
acc = config.accounts[account] = {}
user = new TestAccount(account)
acc.account = user.address
acc.secret = user.master_seed
set_test_account_secrets: (remote, config) ->
# TODO: config.accounts
for nick,acc of config.accounts
# # Index by nickname ...
remote.set_secret nick, acc.secret
# # ... and by account ID
remote.set_secret acc.account, acc.secret
amount_key: (amt) ->
currency = amt.currency().to_json()
issuer = @realias_issuer amt.issuer().to_json()
"#{currency}/#{issuer}"
create_issuer_realiaser: ->
users = @config.accounts
lookup = {}
accounts = []
@@ -49,25 +206,16 @@ exports.LedgerState = class LedgerState
lookup[user.account] = name
realias = new RegExp(accounts.join("|"), "g")
@realias_issuer = (str) -> str.replace(realias, (match) ->lookup[match])
(str) -> str.replace(realias, (match) ->lookup[match])
############################# LEDGER STATE COMPILER ############################
exports.LedgerState = class LedgerState
parse_amount: (amt_val) ->
amt = Amount.from_json(amt_val)
if not amt.is_valid()
amt = Amount.from_human(amt_val)
if not amt.is_valid()
amt = null
amt
parse_amount(amt_val)
amount_key: (amt) ->
currency = amt.currency().to_json()
issuer = @realias_issuer amt.issuer().to_json()
"#{currency}/#{issuer}"
apply: (context)->
@create_accounts_by_issuing_xrp_from_root(context)
@create_trust_limits(context)
@deliver_ious(context)
@am.amount_key amt
record_iou: (account_id, amt)->
key = @amount_key amt
@@ -120,15 +268,26 @@ exports.LedgerState = class LedgerState
"No balance declared for #{account_id}"
for amt_val in account.balance
trust = null
balance_trust = parse_balance_and_trust(amt_val)
if balance_trust?
[amt_val, trust_val] = balance_trust
trust = @parse_amount trust_val
@assert trust != null,
"Trust amount #{trust_val} specified for #{account_id} "
"is not valid"
amt = @parse_amount amt_val
@assert amt != null,
"Balance amount #{amt_val} specified for #{account_id} is not valid"
"Balance amount #{amt_val} specified for #{account_id} "
"is not valid"
if amt.is_native()
xrp_balance = @record_xrp(account_id, amt)
else
@record_iou(account_id, amt)
@record_trust(account_id, amt, true)
@record_trust(account_id, trust ? amt, true)
@assert xrp_balance,
"No XRP balanced declared for #{account_id}"
@@ -249,76 +408,11 @@ exports.LedgerState = class LedgerState
undefined
transactor: (fn, args_list, on_each, callback) ->
if args_list.length == 0
return callback()
setup_alias_manager: ->
@am = new AliasManager(@config, @remote, Object.keys(@declaration.accounts))
@realias_issuer = @am.realias_issuer
if not callback?
callback = on_each
on_each = null
@assert callback?, "Must supply a callback"
finalized = {
n: args_list.length
one: ->
if --finalized.n <= 0
callback()
}
async.concatSeries(args_list, ((args, callback) =>
tx = @remote.transaction()
fn.apply(tx, args)
on_each?(args..., tx) # after payment() offer_create() etc so set_flags works
tx.on("proposed", (m) =>
@assert m.engine_result is "tesSUCCESS", "Transactor failure: #{@pretty_json m}"
callback()
).on('final', (m) =>
finalized.one()
)
.on("error", (m) =>
assert false, pretty_json m
).submit()
),
=> testutils.ledger_close @remote, ->
)
requester: (fn, args_list, on_each, callback, on_results) ->
if not callback?
callback = on_each
on_each = null
@assert callback?, "Must supply a callback"
async.concatSeries(args_list, ((args, callback) =>
req = fn.apply @remote, args
on_each?(args..., req)
req.on("success", (m) =>
if m.status?
@assert m.status is "success", "requester failure: #{@pretty_json m}"
callback(null, m)
).on("error", (m) =>
@assert false, @pretty_json m
).request()
),
(error, results_list) ->
on_results?(results_list);
callback()
)
ensure_config_has_test_accounts: ->
for account of @declaration.accounts
if not @config.accounts[account]?
acc = @config.accounts[account] = {}
user = new TestAccount(account)
acc.account = user.address
acc.secret = user.master_seed
# Index by nickname ...
@remote.set_secret account, acc.secret
# ... and by account ID
@remote.set_secret acc.account, acc.secret
@setup_issuer_realiaser()
pretty_json: (v) ->
pretty_json: (v) =>
@realias_issuer pretty_json(v)
constructor: (declaration, @assert, @remote, @config) ->
@@ -334,13 +428,85 @@ exports.LedgerState = class LedgerState
@iou_payments = [] # {$account_id: []}
@offers = [] # {$account_id: []}
@ensure_config_has_test_accounts()
@setup_alias_manager()
@compile_accounts_balances_and_implicit_trusts()
@compile_explicit_trusts()
@compile_offers()
@check_reserves()
@format_payments()
@format_trusts()
@add_transaction_fees()
compile_to_rpc_commands: ->
passphrase = (src) ->
if src == 'root'
'masterpassphrase'
else
src
make_tx_json = (src, tt) ->
{"Account": UInt160.json_rewrite(src), "TransactionType": tt}
submit_line = (src, tx_json) ->
"build/rippled submit #{passphrase(src)} '#{JSON.stringify tx_json}'"
lines = []
ledger_accept = -> lines.push('build/rippled ledger_accept')
for [src, dst, amount] in @xrp_payments
tx_json = make_tx_json(src, 'Payment')
tx_json.Destination = UInt160.json_rewrite dst
tx_json.Amount = amount.to_json()
lines.push submit_line(src, tx_json)
ledger_accept()
for [src, limit] in @trusts
tx_json = make_tx_json(src, 'TrustSet')
tx_json.LimitAmount = limit.to_json()
lines.push submit_line(src, tx_json)
ledger_accept()
for [src, dst, amount] in @iou_payments
tx_json = make_tx_json(src, 'Payment')
tx_json.Destination = UInt160.json_rewrite dst
tx_json.Amount = amount.to_json()
lines.push submit_line(src, tx_json)
ledger_accept()
for [src, pays, gets, flags] in @offers
tx = new Transaction({secrets: {}})
tx.offer_create(src, pays, gets)
tx.set_flags(flags)
# console.log tx.tx_json
# process.exit()
# tx_json = make_tx_json(src, 'OfferCreate')
# tx_json.TakerPays = pays.to_json()
# tx_json.TakerGets = gets.to_json()
lines.push submit_line(src, tx.tx_json)
ledger_accept()
lines.join('\n')
verifier: (decl) ->
new LedgerVerifier(decl ? @declaration, @remote, @config, @assert, @am)
add_transaction_fees: ->
extra_fees = {}
fee = Amount.from_json('15')
for list in [@trusts, @iou_payments, @offers]
for [src, args...] in list
extra = extra_fees[src]
extra = if extra? then extra.add(fee) else fee
extra_fees[src] = extra
for [src, dst, amount], ix in @xrp_payments
if extra_fees[dst]?
@xrp_payments[ix][2] = amount.add(extra_fees[dst])
setup: (log, done) ->
LOG = (m) ->
@@ -352,80 +518,213 @@ exports.LedgerState = class LedgerState
accounts_apply_arguments = ([ac] for ac, _ of @accounts)
self = this
Dump = (v) => console.log @pretty_json(v)
Dump = ->
reqs = new BulkRequests(@remote, @assert, @pretty_json)
async.waterfall [
(cb) ->
self.transactor(
reqs.transactor(
Transaction::payment,
self.xrp_payments,
((src, dest, amt) ->
LOG("Account `#{src}` creating account `#{dest}` by
making payment of #{amt.to_text_full()}") ),
LOG("Account `#{src}` creating account `#{dest}` by "+
"making payment of #{amt.to_text_full()}") ),
cb)
(cb) ->
self.transactor(
reqs.transactor(
Transaction::ripple_line_set,
self.trusts,
((src, amt) ->
issuer = self.realias_issuer amt.issuer().to_json()
currency = amt.currency().to_json()
LOG("Account `#{src}` trusts account `#{issuer}` for
#{amt.to_text()} #{currency}") ),
LOG("Account `#{src}` trusts account `#{issuer}` for "+
"#{amt.to_text()} #{currency}") ),
cb)
(cb) ->
self.transactor(
reqs.transactor(
Transaction::payment,
self.iou_payments,
((src, dest, amt, tx) ->
LOG("Account `#{src}` is making a payment of #{amt.to_text_full()}
to `#{dest}`") ),
LOG("Account `#{src}` is making a payment of #{amt.to_text_full()} "+
"to `#{dest}`") ),
cb)
(cb) ->
self.transactor(
reqs.transactor(
Transaction::offer_create,
self.offers,
((src, pays, gets, tx) ->
tx.set_flags('Passive')
LOG("Account `#{src}` is selling #{gets.to_text_full()}
for #{pays.to_text_full()}")),
((src, pays, gets, flags, tx) ->
if not tx?
tx = flags
flags = ['Passive']
else
# TODO: icky ;)
delete tx.tx_json.Expiration
tx.set_flags(flags)
LOG("Account `#{src}` is selling #{gets.to_text_full()} "+
"for #{pays.to_text_full()}")),
cb)
(cb) ->
testutils.ledger_close self.remote, cb
(cb) ->
self.requester(Remote::request_account_lines, accounts_apply_arguments,
((acc) ->
LOG("Checking account_lines for #{acc}")),
cb)
(cb) ->
self.requester(Remote::request_account_offers, accounts_apply_arguments,
((acc) ->
LOG("Checking account_offers for #{acc}")),
cb, (results) ->
for [ac], ix in accounts_apply_arguments
account = self.declaration.accounts[ac]
offers_declared = (account.offers ? []).length
actual = results[ix].offers
offers_made = actual.length
if offers_made != offers_declared
shortened = []
for offer in actual
keys = ['taker_pays', 'taker_gets']
pair = (Amount.from_json(offer[k]).to_text_full() for k in keys)
shortened.push pair
shortened_text = self.pretty_json shortened
self.assert offers_made == offers_declared,
"Account #{ac} has failed offer\n"+
"Declared: #{pretty_json account.offers}\n"+
"Actual: #{shortened_text}"
)
(cb) ->
self.requester(Remote::request_account_info, accounts_apply_arguments,
((acc) ->
LOG("Checking account_info for #{acc}")),
cb)
], (error) ->
assert !error,
"There was an error @ #{self.what}"
done()
################################ LEDGER VERIFIER ###############################
ensure = (account_id, obj, val) ->
if not obj[account_id]?
obj[account_id] = val ? {}
obj[account_id]
exports.LedgerVerifier = class LedgerVerifier
constructor: (@declaration, @remote, @config, @assert, @am) ->
@am ?= new AliasManager(@config, @remote, Object.keys(@declaration.accounts))
@requester = new BulkRequests(@remote, @assert, @am.pretty_json)
@compile_declaration()
verify_lines: (errors, account_lines) ->
for account in account_lines
# For test sweet ;)
account_alias = @am.lookup_alias account.account
for line in account.lines
peer_alias = @am.lookup_alias line.account
key = "#{line.currency}/#{peer_alias}"
asserted = @iou_balances[account_alias]?[key]
if asserted?
actual = Amount.from_json(
"#{line.balance}/#{line.currency}/#{line.account}")
if not asserted.equals(actual)
balance = (((errors[account_alias] ?= {})['balance'] ?= {}))
balance[key] =
expected: asserted.to_text()
actual: actual.to_text()
asserted = @trusts[account_alias]?[key]
if asserted?
actual = Amount.from_json(
"#{line.limit}/#{line.currency}/#{line.account}")
if not asserted.equals(actual)
limit = (((errors[account_alias] ?= {})['limit'] ?= {}))
limit[key] =
expected: asserted.to_text()
actual: actual.to_text()
verify_infos: (errors, account_infos) ->
for account in account_infos
root = account.account_data
account_alias = @am.lookup_alias root.Account
asserted = @xrp_balances[account_alias]
if asserted?
actual = Amount.from_json root.Balance
if not asserted.equals(actual)
balance = (((errors[account_alias] ?= {})['balance'] ?= {}))
balance['XRP'] =
expected: asserted.to_human()
actual: actual.to_human()
verify_offers: (errors, account_offers) ->
for account in account_offers
account_alias = @am.lookup_alias account.account
get_errors = -> (((errors[account_alias] ?= {})['offers'] ?= []))
assertions = @offers[account_alias]
continue if not assertions?
amount_text = (amt) => @am.realias_issuer amt.to_text_full()
for asserted, ix in assertions
offer = account.offers[ix]
if not offer?
get_errors().push {expected: asserted, actual: 'missing'}
continue
else
# expected_*
[epays, egets] = (parse_amount a for a in asserted)
# actual_*
apays = Amount.from_json offer.taker_pays
agets = Amount.from_json offer.taker_gets
err = {}
if not epays.equals apays
pay_err = (err['taker_pays'] = {})
pay_err['expected'] = amount_text epays
pay_err['actual'] = amount_text apays
if not egets.equals agets
get_err = (err['taker_gets'] = {})
get_err['expected'] = amount_text egets
get_err['actual'] = amount_text agets
if Object.keys(err).length > 0
offer_errors = get_errors()
offer_errors.push err
verify: (account_infos, account_lines, account_offers) ->
errors = {}
# console.log @am.pretty_json account_infos
# console.log @am.pretty_json account_lines
# console.log @am.pretty_json account_offers
@verify_infos errors, account_infos
@verify_lines errors, account_lines
@verify_offers errors, account_offers
errors
do_verify: (done) ->
args_from_keys = (obj) -> ([a] for a in Object.keys obj)
reqs = @requester
lines_args = args_from_keys @iou_balances
info_args = args_from_keys @xrp_balances
offers_args = args_from_keys @offers
async.series [
(cb) ->
reqs.requester(Remote::request_account_info, info_args, cb)
(cb) ->
reqs.requester(Remote::request_account_lines, lines_args, cb)
(cb) ->
reqs.requester(Remote::request_account_offers, offers_args, cb)
], (error, results) =>
assert !error,
"There was an error @ #{error}"
done(@verify(results...))
compile_declaration: ->
@offers = {}
@xrp_balances = {}
@iou_balances = {}
@trusts = {}
@realias_issuer = @am.realias_issuer
record_amount = (account_id, to, amt) =>
key = @am.amount_key amt
ensure(account_id, to)[key] = amt
for account_id, account of @declaration.accounts
if account.offers?
@offers[account_id] = account.offers
if Array.isArray(account.balance)
for value in account.balance
balance = new Balance(value)
if balance.is_native
@xrp_balances[account_id] = balance.amount
else
if balance.limit?
record_amount account_id, @trusts, balance.limit
record_amount account_id, @iou_balances, balance.amount

View File

@@ -0,0 +1,19 @@
mocha = require("mocha")
// Stash a reference away to this
old_loader = mocha.prototype.loadFiles
if (!old_loader.monkey_patched) {
// Gee thanks Mocha ...
mocha.prototype.loadFiles = function() {
try {
old_loader.apply(this, arguments);
} catch (e) {
// Normally mocha just silently bails
console.error(e.stack);
// We throw, so mocha doesn't continue trying to run the test suite
throw e;
}
}
mocha.prototype.loadFiles.monkey_patched = true;
};

View File

@@ -1,3 +1,4 @@
--require ./test/mocha-loader-patch.js
--reporter spec
--compilers coffee:coffee-script
--ui tdd

114
test/offer-tests-json.js Normal file
View File

@@ -0,0 +1,114 @@
module.exports = {
"Partially crossed completely via bridging": {
"pre_ledger": {"accounts": {"G1": {"balance": ["1000.0"]},
"G2": {"balance": ["1000.0"]},
"alice": {"balance": ["500000.0", "200/USD/G1"],
"offers": [["100/USD/G1", "88.0"]]},
"bob": {"balance": ["500000.0", "500/USD/G2"],
"offers": [["88.0", "100/USD/G2"]]},
"takerJoe": {"balance": ["500000.0", "500/USD/G1"]}}},
"offer": ["takerJoe", "500/USD/G2", "500/USD/G1"],
"post_ledger": {"accounts": {"takerJoe": {"balance": ["400/USD/G1", "100/USD/G2"],
"offers": [["400/USD/G2", "400/USD/G1"]]}}}
},
"Partially crossed (halt)": {
"pre_ledger": {"accounts": {"G1": {"balance": ["1000.0"]},
"G2": {"balance": ["1000.0"]},
"alice": {"balance": ["500000.0", "200/USD/G1"],
"offers": [["100/USD/G1", "88.0"], ["100/USD/G1", "88.0"]]},
//----/ (2) (3) (5) there's no offers left. Halt
"bob": {"balance": ["500000.0", "500/USD/G2"],
"offers": [["88.0", "100/USD/G2"]]},
// (4)
"takerJoe": {"balance": ["500000.0", "500/USD/G1"]}}},
// (1)
"offer": ["takerJoe", "500/USD/G2", "500/USD/G1"],
// 500,000-88 200+100/USD/G1
"post_ledger": {"accounts": {"alice": {"balance": ["499912.0", "300/USD/G1"],
"offers": [["100/USD/G1", "88.0"]]},
"bob": {"balance": ["500088.0", "400/USD/G2"],
"offers": [/*["88.0", "100/USD/G2"]*/]},
"takerJoe": {"balance": ["100/USD/G2", "400/USD/G1"],
"offers": [["400/USD/G2", "400/USD/G1"]]}}}
},
"Partially crossed completely via bridging (Sell)": {
"pre_ledger": {"accounts": {"G1": {"balance": ["1000.0"]},
"G2": {"balance": ["1000.0"]},
"alice": {"balance": ["500000.0", "200/USD/G1"],
"offers": [["200/USD/G1", "176.0", "Sell"]]},
"bob": {"balance": ["500000.0", "500/USD/G2"],
"offers": [["88.0", "100/USD/G2"]]},
"takerJoe": {"balance": ["500000.0", "500/USD/G1"]}}},
"offer": ["takerJoe", "500/USD/G2", "500/USD/G1", "Sell"],
"post_ledger": {"accounts": {"alice": {"balance": ["499912.0", "299.9999999999999/USD/G1"],
"offers": [["100.0000000000001/USD/G1", "88.0"]]},
"takerJoe": {"balance": ["100/USD/G2", "400.0000000000001/USD/G1"],
"offers": [["400.0000000000001/USD/G2", "400.0000000000001/USD/G1"]]}}}
},
"Completely crossed via bridging + direct": {
"pre_ledger": {"accounts": {"G1": {"balance": ["1000.0"]},
"G2": {"balance": ["1000.0"]},
"alice": {"balance": ["500000.0", "500/USD/G1", "500/USD/G2"],
"offers": [["50/USD/G1", "50/USD/G2"],
["49/USD/G1", "50/USD/G2"],
["48/USD/G1", "50/USD/G2"],
["47/USD/G1", "50/USD/G2"],
["46/USD/G1", "50/USD/G2"],
["45/USD/G1", "50/USD/G2"],
["44/USD/G1", "50/USD/G2"],
["43/USD/G1", "50/USD/G2"],
["100/USD/G1", "88.0"]]},
"bob": {"balance": ["500000.0", "500/USD/G2"],
"offers": [["88.0", "100/USD/G2"]]},
"takerJoe": {"balance": ["500000.0", "600/USD/G1"]}}},
"offer": ["takerJoe", "500/USD/G2", "500/USD/G1"],
"post_ledger": {"accounts": {"takerJoe": {"balance": ["500/USD/G2", "128/USD/G1"]}}}
},
"Partially crossed via bridging + direct": {
"pre_ledger": {"accounts": {"G1": {"balance": ["1000.0"]},
"G2": {"balance": ["1000.0"]},
"alice": {"balance": ["500000.0", "500/USD/G1", "500/USD/G2"],
"offers": [["372/USD/G1", "400/USD/G2"],
["100/USD/G1", "88.0"]]},
"bob": {"balance": ["500000.0", "500/USD/G2"],
"offers": [["88.0", "100/USD/G2"]]},
"takerJoe": {"balance": ["500000.0", "600/USD/G1"]}}},
"offer": ["takerJoe", "600/USD/G2", "600/USD/G1"],
"post_ledger": {"accounts": {"takerJoe": {"balance": ["500/USD/G2", "128/USD/G1"],
"offers": [["100/USD/G2", "100/USD/G1"]]}}}
},
"Partially crossed via bridging + direct": {
"pre_ledger": {"accounts": {"G1": {"balance": ["1000.0"]},
"G2": {"balance": ["1000.0"]},
"alice": {"balance": ["500000.0", "500/USD/G1", "500/USD/G2"],
"offers": [["372/USD/G1", "400/USD/G2"],
["100/USD/G1", "88.0"]]},
"bob": {"balance": ["500000.0", "500/USD/G2"],
"offers": [["88.0", "100/USD/G2"]]},
"takerJoe": {"balance": ["500000.0", "600/USD/G1"]}}},
"offer": ["takerJoe", "600/USD/G2", "600/USD/G1", "Sell"],
"post_ledger": {"accounts": {"takerJoe": {"balance": ["500/USD/G2", "128/USD/G1"],
"offers": [["128/USD/G2", "128/USD/G1"]]}}}
}
}

View File

@@ -1,4 +1,4 @@
{
module.exports = {
"Path Tests #1 (XRP -> XRP) and #2 (XRP -> IOU)": {
"ledger": {"accounts": {"A1": {"balance": ["100000.0",

View File

@@ -30,6 +30,7 @@ function Server(name, config, verbose) {
this.started = false;
this.quiet = !verbose;
this.stopping = false;
this.ledger_file = null;
var nodejs_version = process.version.match(/^v(\d+)+\.(\d+)\.(\d+)$/).slice(1,4);
var wanted_version = [ 0, 8, 18 ];
@@ -78,6 +79,10 @@ Server.prototype._writeConfig = function(done) {
'utf8', done);
};
Server.prototype.set_ledger_file = function(fn) {
this.ledger_file = __dirname + '/fixtures/' + fn;
}
// Spawn the server.
Server.prototype._serverSpawnSync = function() {
var self = this;
@@ -88,6 +93,10 @@ Server.prototype._serverSpawnSync = function() {
"--conf=rippled.cfg"
];
if (this.ledger_file != null) {
args.push('--ledgerfile=' + this.ledger_file)
};
var options = {
cwd: this.serverPath(),
env: process.env,
@@ -177,6 +186,7 @@ Server.prototype.stop = function () {
// Update the on exit to invoke done.
this.child.on('exit', function (code, signal) {
if (!self.quiet) console.log("server: stop: server exited");
self.stopped = true;
self.emit('stopped');
delete self.child;
});

View File

@@ -96,6 +96,11 @@ function build_setup(opts, host) {
data.server = Server.from_config(host, server_config, !!opts.verbose_server);
// Setting undefined is a noop here
if (data.opts.ledger_file != null) {
data.server.set_ledger_file(data.opts.ledger_file);
};
data.server.once('started', function() {
callback();
});
@@ -164,7 +169,11 @@ function build_teardown(host) {
}
];
if (!opts.no_server && data.server.stopped) {
done()
} else {
async.series(series, done);
}
};
return teardown;