mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-20 02:55:50 +00:00
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:
committed by
Nik Bougalis
parent
0848e348bb
commit
e14c700c60
177
test/autobridge-test.coffee
Normal file
177
test/autobridge-test.coffee
Normal 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
106
test/batmans-belt.coffee
Normal 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()
|
||||||
@@ -32,6 +32,8 @@ exports.servers = {
|
|||||||
'rpc_ip' : "0.0.0.0",
|
'rpc_ip' : "0.0.0.0",
|
||||||
'rpc_port' : 5005,
|
'rpc_port' : 5005,
|
||||||
'local_sequence' : true,
|
'local_sequence' : true,
|
||||||
|
'trace' : false,
|
||||||
|
// 'trace' : true,
|
||||||
'local_fee' : true,
|
'local_fee' : true,
|
||||||
// 'validation_seed' : "shhDFVsmS2GSu5vUyZSPXYfj1r79h",
|
// 'validation_seed' : "shhDFVsmS2GSu5vUyZSPXYfj1r79h",
|
||||||
// 'validators' : "n9L8LZZCwsdXzKUN9zoVxs4YznYXZ9hEhsQZY7aVpxtFaSceiyDZ beta",
|
// 'validators' : "n9L8LZZCwsdXzKUN9zoVxs4YznYXZ9hEhsQZY7aVpxtFaSceiyDZ beta",
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ The tests are written in a declarative style:
|
|||||||
$ signifies an order book rather than account
|
$ 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 ###################################
|
#################################### HELPERS ###################################
|
||||||
@@ -455,8 +455,10 @@ define_suites = (path_finding_cases) ->
|
|||||||
A0 = (new TestAccount('A0')).address
|
A0 = (new TestAccount('A0')).address
|
||||||
assert A0 == 'rBmhuVAvi372AerwzwERGjhLjqkMmAwxX'
|
assert A0 == 'rBmhuVAvi372AerwzwERGjhLjqkMmAwxX'
|
||||||
|
|
||||||
path_finding_cases_string = fs.readFileSync(__dirname + "/path-tests.json")
|
try
|
||||||
path_finding_cases = JSON.parse path_finding_cases_string
|
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
|
# 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.
|
# gateway and holds its currency, and a destination that trusts the other.
|
||||||
135
test/discrepancy-test.coffee
Normal file
135
test/discrepancy-test.coffee
Normal 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
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
644
test/fixtures/ledger-7145315.json
vendored
Normal 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
813
test/freeze-test.coffee
Normal 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()
|
||||||
152
test/ledger-state-test.coffee
Normal file
152
test/ledger-state-test.coffee
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,15 +4,15 @@
|
|||||||
|
|
||||||
async = require 'async'
|
async = require 'async'
|
||||||
assert = require 'assert'
|
assert = require 'assert'
|
||||||
{
|
{
|
||||||
Amount
|
Amount
|
||||||
Remote
|
Remote
|
||||||
Seed
|
Seed
|
||||||
Base
|
Base
|
||||||
|
UInt160
|
||||||
Transaction
|
Transaction
|
||||||
sjcl
|
sjcl
|
||||||
} = require 'ripple-lib'
|
} = require 'ripple-lib'
|
||||||
{Server} = require './server'
|
|
||||||
testutils = require './testutils'
|
testutils = require './testutils'
|
||||||
|
|
||||||
#################################### HELPERS ###################################
|
#################################### HELPERS ###################################
|
||||||
@@ -36,10 +36,167 @@ exports.TestAccount = class TestAccount
|
|||||||
@passphrase = passphrase
|
@passphrase = passphrase
|
||||||
[@address, @master_seed, @key_pair] = @derive_pair(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
|
exports.parse_amount = parse_amount = (amt_val) ->
|
||||||
setup_issuer_realiaser: ->
|
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
|
users = @config.accounts
|
||||||
lookup = {}
|
lookup = {}
|
||||||
accounts = []
|
accounts = []
|
||||||
@@ -49,25 +206,16 @@ exports.LedgerState = class LedgerState
|
|||||||
lookup[user.account] = name
|
lookup[user.account] = name
|
||||||
|
|
||||||
realias = new RegExp(accounts.join("|"), "g")
|
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) ->
|
parse_amount: (amt_val) ->
|
||||||
amt = Amount.from_json(amt_val)
|
parse_amount(amt_val)
|
||||||
if not amt.is_valid()
|
|
||||||
amt = Amount.from_human(amt_val)
|
|
||||||
if not amt.is_valid()
|
|
||||||
amt = null
|
|
||||||
amt
|
|
||||||
|
|
||||||
amount_key: (amt) ->
|
amount_key: (amt) ->
|
||||||
currency = amt.currency().to_json()
|
@am.amount_key amt
|
||||||
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)
|
|
||||||
|
|
||||||
record_iou: (account_id, amt)->
|
record_iou: (account_id, amt)->
|
||||||
key = @amount_key amt
|
key = @amount_key amt
|
||||||
@@ -120,15 +268,26 @@ exports.LedgerState = class LedgerState
|
|||||||
"No balance declared for #{account_id}"
|
"No balance declared for #{account_id}"
|
||||||
|
|
||||||
for amt_val in account.balance
|
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
|
amt = @parse_amount amt_val
|
||||||
@assert amt != null,
|
@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()
|
if amt.is_native()
|
||||||
xrp_balance = @record_xrp(account_id, amt)
|
xrp_balance = @record_xrp(account_id, amt)
|
||||||
else
|
else
|
||||||
@record_iou(account_id, amt)
|
@record_iou(account_id, amt)
|
||||||
@record_trust(account_id, amt, true)
|
@record_trust(account_id, trust ? amt, true)
|
||||||
|
|
||||||
@assert xrp_balance,
|
@assert xrp_balance,
|
||||||
"No XRP balanced declared for #{account_id}"
|
"No XRP balanced declared for #{account_id}"
|
||||||
@@ -160,8 +319,8 @@ exports.LedgerState = class LedgerState
|
|||||||
"Account #{account_id}s doesn't have enough xrp to place #{offer}"
|
"Account #{account_id}s doesn't have enough xrp to place #{offer}"
|
||||||
else
|
else
|
||||||
key = @amount_key gets_amt
|
key = @amount_key gets_amt
|
||||||
|
|
||||||
if key.split('/')[1] != account_id
|
if key.split('/')[1] != account_id
|
||||||
key_offers = @ensure(key, offers, {})
|
key_offers = @ensure(key, offers, {})
|
||||||
|
|
||||||
total = key_offers.total ?= Amount.from_json("0/#{key}")
|
total = key_offers.total ?= Amount.from_json("0/#{key}")
|
||||||
@@ -239,7 +398,7 @@ exports.LedgerState = class LedgerState
|
|||||||
src = @realias_issuer amt.issuer().to_json()
|
src = @realias_issuer amt.issuer().to_json()
|
||||||
dst = account_id
|
dst = account_id
|
||||||
@iou_payments.push [src, dst, amt]
|
@iou_payments.push [src, dst, amt]
|
||||||
|
|
||||||
undefined
|
undefined
|
||||||
|
|
||||||
format_trusts: ->
|
format_trusts: ->
|
||||||
@@ -249,76 +408,11 @@ exports.LedgerState = class LedgerState
|
|||||||
|
|
||||||
undefined
|
undefined
|
||||||
|
|
||||||
transactor: (fn, args_list, on_each, callback) ->
|
setup_alias_manager: ->
|
||||||
if args_list.length == 0
|
@am = new AliasManager(@config, @remote, Object.keys(@declaration.accounts))
|
||||||
return callback()
|
@realias_issuer = @am.realias_issuer
|
||||||
|
|
||||||
if not callback?
|
pretty_json: (v) =>
|
||||||
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) ->
|
|
||||||
@realias_issuer pretty_json(v)
|
@realias_issuer pretty_json(v)
|
||||||
|
|
||||||
constructor: (declaration, @assert, @remote, @config) ->
|
constructor: (declaration, @assert, @remote, @config) ->
|
||||||
@@ -334,13 +428,85 @@ exports.LedgerState = class LedgerState
|
|||||||
@iou_payments = [] # {$account_id: []}
|
@iou_payments = [] # {$account_id: []}
|
||||||
@offers = [] # {$account_id: []}
|
@offers = [] # {$account_id: []}
|
||||||
|
|
||||||
@ensure_config_has_test_accounts()
|
@setup_alias_manager()
|
||||||
@compile_accounts_balances_and_implicit_trusts()
|
@compile_accounts_balances_and_implicit_trusts()
|
||||||
@compile_explicit_trusts()
|
@compile_explicit_trusts()
|
||||||
@compile_offers()
|
@compile_offers()
|
||||||
@check_reserves()
|
@check_reserves()
|
||||||
@format_payments()
|
@format_payments()
|
||||||
@format_trusts()
|
@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) ->
|
setup: (log, done) ->
|
||||||
LOG = (m) ->
|
LOG = (m) ->
|
||||||
@@ -352,80 +518,213 @@ exports.LedgerState = class LedgerState
|
|||||||
accounts_apply_arguments = ([ac] for ac, _ of @accounts)
|
accounts_apply_arguments = ([ac] for ac, _ of @accounts)
|
||||||
self = this
|
self = this
|
||||||
|
|
||||||
|
Dump = (v) => console.log @pretty_json(v)
|
||||||
|
Dump = ->
|
||||||
|
|
||||||
|
reqs = new BulkRequests(@remote, @assert, @pretty_json)
|
||||||
|
|
||||||
async.waterfall [
|
async.waterfall [
|
||||||
(cb) ->
|
(cb) ->
|
||||||
self.transactor(
|
reqs.transactor(
|
||||||
Transaction::payment,
|
Transaction::payment,
|
||||||
self.xrp_payments,
|
self.xrp_payments,
|
||||||
((src, dest, amt) ->
|
((src, dest, amt) ->
|
||||||
LOG("Account `#{src}` creating account `#{dest}` by
|
LOG("Account `#{src}` creating account `#{dest}` by "+
|
||||||
making payment of #{amt.to_text_full()}") ),
|
"making payment of #{amt.to_text_full()}") ),
|
||||||
cb)
|
cb)
|
||||||
(cb) ->
|
(cb) ->
|
||||||
self.transactor(
|
reqs.transactor(
|
||||||
Transaction::ripple_line_set,
|
Transaction::ripple_line_set,
|
||||||
self.trusts,
|
self.trusts,
|
||||||
((src, amt) ->
|
((src, amt) ->
|
||||||
issuer = self.realias_issuer amt.issuer().to_json()
|
issuer = self.realias_issuer amt.issuer().to_json()
|
||||||
currency = amt.currency().to_json()
|
currency = amt.currency().to_json()
|
||||||
LOG("Account `#{src}` trusts account `#{issuer}` for
|
LOG("Account `#{src}` trusts account `#{issuer}` for "+
|
||||||
#{amt.to_text()} #{currency}") ),
|
"#{amt.to_text()} #{currency}") ),
|
||||||
cb)
|
cb)
|
||||||
(cb) ->
|
(cb) ->
|
||||||
self.transactor(
|
reqs.transactor(
|
||||||
Transaction::payment,
|
Transaction::payment,
|
||||||
self.iou_payments,
|
self.iou_payments,
|
||||||
((src, dest, amt, tx) ->
|
((src, dest, amt, tx) ->
|
||||||
LOG("Account `#{src}` is making a payment of #{amt.to_text_full()}
|
LOG("Account `#{src}` is making a payment of #{amt.to_text_full()} "+
|
||||||
to `#{dest}`") ),
|
"to `#{dest}`") ),
|
||||||
cb)
|
cb)
|
||||||
(cb) ->
|
(cb) ->
|
||||||
self.transactor(
|
reqs.transactor(
|
||||||
Transaction::offer_create,
|
Transaction::offer_create,
|
||||||
self.offers,
|
self.offers,
|
||||||
((src, pays, gets, tx) ->
|
((src, pays, gets, flags, tx) ->
|
||||||
tx.set_flags('Passive')
|
if not tx?
|
||||||
LOG("Account `#{src}` is selling #{gets.to_text_full()}
|
tx = flags
|
||||||
for #{pays.to_text_full()}")),
|
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)
|
||||||
(cb) ->
|
(cb) ->
|
||||||
testutils.ledger_close self.remote, 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) ->
|
], (error) ->
|
||||||
assert !error,
|
assert !error,
|
||||||
"There was an error @ #{self.what}"
|
"There was an error @ #{self.what}"
|
||||||
done()
|
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
|
||||||
|
|||||||
19
test/mocha-loader-patch.js
Normal file
19
test/mocha-loader-patch.js
Normal 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;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
--require ./test/mocha-loader-patch.js
|
||||||
--reporter spec
|
--reporter spec
|
||||||
--compilers coffee:coffee-script
|
--compilers coffee:coffee-script
|
||||||
--ui tdd
|
--ui tdd
|
||||||
|
|||||||
114
test/offer-tests-json.js
Normal file
114
test/offer-tests-json.js
Normal 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"]]}}}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
module.exports = {
|
||||||
"Path Tests #1 (XRP -> XRP) and #2 (XRP -> IOU)": {
|
"Path Tests #1 (XRP -> XRP) and #2 (XRP -> IOU)": {
|
||||||
|
|
||||||
"ledger": {"accounts": {"A1": {"balance": ["100000.0",
|
"ledger": {"accounts": {"A1": {"balance": ["100000.0",
|
||||||
@@ -25,11 +25,12 @@ var nodeutils = require("./nodeutils");
|
|||||||
|
|
||||||
// Create a server object
|
// Create a server object
|
||||||
function Server(name, config, verbose) {
|
function Server(name, config, verbose) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.started = false;
|
this.started = false;
|
||||||
this.quiet = !verbose;
|
this.quiet = !verbose;
|
||||||
this.stopping = false;
|
this.stopping = false;
|
||||||
|
this.ledger_file = null;
|
||||||
|
|
||||||
var nodejs_version = process.version.match(/^v(\d+)+\.(\d+)\.(\d+)$/).slice(1,4);
|
var nodejs_version = process.version.match(/^v(\d+)+\.(\d+)\.(\d+)$/).slice(1,4);
|
||||||
var wanted_version = [ 0, 8, 18 ];
|
var wanted_version = [ 0, 8, 18 ];
|
||||||
@@ -78,6 +79,10 @@ Server.prototype._writeConfig = function(done) {
|
|||||||
'utf8', done);
|
'utf8', done);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Server.prototype.set_ledger_file = function(fn) {
|
||||||
|
this.ledger_file = __dirname + '/fixtures/' + fn;
|
||||||
|
}
|
||||||
|
|
||||||
// Spawn the server.
|
// Spawn the server.
|
||||||
Server.prototype._serverSpawnSync = function() {
|
Server.prototype._serverSpawnSync = function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
@@ -88,6 +93,10 @@ Server.prototype._serverSpawnSync = function() {
|
|||||||
"--conf=rippled.cfg"
|
"--conf=rippled.cfg"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (this.ledger_file != null) {
|
||||||
|
args.push('--ledgerfile=' + this.ledger_file)
|
||||||
|
};
|
||||||
|
|
||||||
var options = {
|
var options = {
|
||||||
cwd: this.serverPath(),
|
cwd: this.serverPath(),
|
||||||
env: process.env,
|
env: process.env,
|
||||||
@@ -103,8 +112,8 @@ Server.prototype._serverSpawnSync = function() {
|
|||||||
this.config.rippled_path,
|
this.config.rippled_path,
|
||||||
args.join(" "),
|
args.join(" "),
|
||||||
this.configPath());
|
this.configPath());
|
||||||
|
|
||||||
|
|
||||||
var stderr = [];
|
var stderr = [];
|
||||||
self.child.stderr.on('data', function(buf) { stderr.push(buf); });
|
self.child.stderr.on('data', function(buf) { stderr.push(buf); });
|
||||||
|
|
||||||
@@ -177,6 +186,7 @@ Server.prototype.stop = function () {
|
|||||||
// Update the on exit to invoke done.
|
// Update the on exit to invoke done.
|
||||||
this.child.on('exit', function (code, signal) {
|
this.child.on('exit', function (code, signal) {
|
||||||
if (!self.quiet) console.log("server: stop: server exited");
|
if (!self.quiet) console.log("server: stop: server exited");
|
||||||
|
self.stopped = true;
|
||||||
self.emit('stopped');
|
self.emit('stopped');
|
||||||
delete self.child;
|
delete self.child;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -96,6 +96,11 @@ function build_setup(opts, host) {
|
|||||||
|
|
||||||
data.server = Server.from_config(host, server_config, !!opts.verbose_server);
|
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() {
|
data.server.once('started', function() {
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
@@ -164,7 +169,11 @@ function build_teardown(host) {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
async.series(series, done);
|
if (!opts.no_server && data.server.stopped) {
|
||||||
|
done()
|
||||||
|
} else {
|
||||||
|
async.series(series, done);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return teardown;
|
return teardown;
|
||||||
|
|||||||
Reference in New Issue
Block a user