Merge branch 'develop' of github.com:ripple/rippled into develop

This commit is contained in:
Justin Lynn
2013-10-24 19:11:17 -07:00
8 changed files with 1315 additions and 6 deletions

View File

@@ -13,14 +13,16 @@
"ripple-lib": "0.7.25",
"async": "~0.2.9",
"extend": "~1.2.0",
"simple-jsonrpc": "~0.0.2"
"simple-jsonrpc": "~0.0.2",
"deep-equal": "0.0.0"
},
"devDependencies": {
"coffee-script": "~1.6.3",
"mocha": "~1.13.0"
},
"scripts": {
"test": "mocha --reporter spec --ui tdd --timeout 10000 --slow 600 test/*-test.js"
"test": "mocha test/*-test.{js,coffee}"
},
"repository": {

389
test/ledger-state.coffee Normal file
View File

@@ -0,0 +1,389 @@
################################### REQUIRES ###################################
# This gives coffee-script proper file/lines in the exceptions
async = require("async")
assert = require 'assert'
Amount = require("ripple-lib").Amount
Remote = require("ripple-lib").Remote
Seed = require("ripple-lib").Seed
Base = require("ripple-lib").Base
Transaction = require("ripple-lib").Transaction
sjcl = require("ripple-lib").sjcl
Server = require("./server").Server
testutils = require("./testutils")
#################################### HELPERS ###################################
pretty_json = (v) -> JSON.stringify(v, undefined, 2)
exports.TestAccount = class TestAccount
SHA256_RIPEMD160: (bits) ->
sjcl.hash.ripemd160.hash sjcl.hash.sha256.hash(bits)
get_address: (passphrase) ->
key_pair = Seed.from_json(passphrase).get_key()
pubKey = sjcl.codec.hex.toBits key_pair.to_hex_pub()
Base.encode_check(0,sjcl.codec.bytes.fromBits(@SHA256_RIPEMD160 pubKey))
constructor: (passphrase) ->
@passphrase = passphrase
@address = @get_address(passphrase)
############################# LEDGER STATE COMPILER ############################
exports.LedgerState = class LedgerState
setup_issuer_realiaser: ->
users = @config.accounts
lookup = {}
accounts = []
for name, user of users
accounts.push user.account
lookup[user.account] = name
realias = new RegExp(accounts.join("|"), "g")
@realias_issuer = (str) -> str.replace(realias, (match) ->lookup[match])
parse_amount: (amt_val) ->
try
amt = Amount.from_json(amt_val)
if not amt.is_valid()
throw new Error()
catch e
try
amt = Amount.from_human(amt_val)
if not amt.is_valid()
throw new Error()
catch e
amt = null
amt
amount_key: (amt) ->
currency = amt.currency().to_json()
issuer = @realias_issuer amt.issuer().to_json()
key = "#{currency}/#{issuer}"
key.issuer = issuer
key
apply: (context)->
@create_accounts_by_issuing_xrp_from_root(context)
@create_trust_limits(context)
@deliver_ious(context)
record_iou: (account_id, amt)->
key = @amount_key amt
@assert @declaration.accounts[key.split('/')[1]]?,
"Account for #{key} does not exist"
a_ious = @ensure account_id, @ious
@assert !a_ious[key]?,
"Account #{account_id} has more than one amount for #{key}"
a_ious[key] = amt
ensure: (account_id, obj, val) ->
if not obj[account_id]?
obj[account_id] = val ? {}
obj[account_id]
record_xrp: (account_id, amt)->
@assert !@accounts[account_id]?,
"Already declared XRP for #{account_id}"
@accounts[account_id] = amt
record_trust: (account_id, amt, is_balance) ->
key = @amount_key amt
a_trusts = @ensure account_id, @trusts_by_ci
if a_trusts[key]? and !is_balance
cmp = amt.compareTo a_trusts[key]
@assert cmp != - 1,
"Account #{account_id} trust is less than balance for #{key}"
a_trusts[key] = amt
compile_explicit_trusts: ->
for account_id, account of @declaration.accounts
if not account.trusts?
continue
for amt_val in account.trusts
amt = @parse_amount amt_val
@assert amt != null and !amt.is_native(),
"Trust amount #{amt_val} specified for #{account_id} is not valid"
@record_trust(account_id, amt, false)
compile_accounts_balances_and_implicit_trusts: ->
for account_id, account of @declaration.accounts
xrp_balance = null
@assert account.balance?,
"No balance declared for #{account_id}"
for amt_val in account.balance
amt = @parse_amount amt_val
@assert amt != null,
"Balance amount #{amt_val} specified for #{account_id} is not valid"
if amt.is_native()
xrp_balance = @record_xrp(account_id, amt)
else
@record_iou(account_id, amt)
@record_trust(account_id, amt, true)
@assert xrp_balance,
"No XRP balanced declared for #{account_id}"
compile_offers: ->
for account_id, account of @declaration.accounts
if not account.offers?
continue
for offer in account.offers
[pays, gets, splat...] = offer
gets_amt = @parse_amount gets
@assert gets_amt != null,
"For account #{account_id} taker_gets amount #{gets} is invalid"
pays_amt = @parse_amount pays
@assert pays_amt != null,
"For account #{account_id} taker_pays amount #{pays} is invalid"
a_offers = @ensure(account_id, @offers_by_ci)
a_offers = @ensure(account_id, @offers_by_ci)
offers_all = @ensure('offers', a_offers, [])
if gets_amt.is_native()
total = a_offers.xrp_total ?= new Amount.from_json('0')
new_total = total.add(gets_amt)
@assert @accounts[account_id].compareTo(new_total) != - 1,
"Account #{account_id}s doesn't have enough xrp to place #{offer}"
else
key = @amount_key gets_amt
key_offers = @ensure(key, a_offers, {})
total = key_offers.total ?= Amount.from_json("0/#{key}")
new_total = total.add(gets_amt)
a_ious = @ensure(account_id, @ious)
@assert a_ious[key]?,
"Account #{account_id} doesn't hold any #{key}"
@assert a_ious[key].compareTo(new_total) != - 1,
"Account #{account_id} doesn't have enough #{key} to place #{offer}"
key_offers.total = new_total
offers_all.push [pays_amt, gets_amt, splat...]
@offers = []
for account_id, obj of @offers_by_ci
for offer in obj.offers
sliced = offer[0..]
sliced.unshift account_id
@offers.push sliced
# @offers[account_id] = obj.offers
base_reserve: ->
@declaration.reserve?.base ? "50.0"
incr_reserve: ->
@declaration.reserve?.base ? "12.5"
check_reserves: ->
base_reserve_amt = @base_reserve()
incr_reserve_amt = @incr_reserve()
base_reserve = @parse_amount base_reserve_amt
inc_reserve = @parse_amount incr_reserve_amt
@assert base_reserve != null,
"Base reserve amount #{base_reserve_amt} is invalid"
@assert base_reserve != null,
"incremental amount #{incr_reserve_amt} is invalid"
for account_id, account of @declaration.accounts
total_needed = base_reserve.clone()
owner_count = 0
a_offers = @offers_by_ci[account_id]
if a_offers?
if a_offers.xrp_total?
total_needed = total_needed.add a_offers.xrp_total
if a_offers.offers?
owner_count += @offers_by_ci[account_id].offers.length
if @trusts_by_ci[account_id]?
owner_count += Object.keys(@trusts_by_ci[account_id]).length
owner_count_amount = Amount.from_json(String(owner_count))
inc_reserve_n = owner_count_amount.multiply(inc_reserve)
total_needed = total_needed.add(inc_reserve_n)
@assert @accounts[account_id].compareTo total_needed != - 1,
"Account #{account_id} needs more XRP for reserve"
@reserves[account_id] = total_needed
format_payments: ->
# We do these first as the following @ious need xrp to issue ious ;0
for account_id, xrps of @accounts
@xrp_payments.push ['root', account_id, xrps]
for account_id, ious of @ious
for curr_issuer, amt of ious
src = @realias_issuer amt.issuer().to_json()
dst = account_id
@iou_payments.push [src, dst, amt]
format_trusts: ->
for account_id, trusts of @trusts_by_ci
for curr_issuer, amt of trusts
@trusts.push [account_id, amt]
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()
}
async.concatSeries(args_list, ((args, callback) =>
tx = @remote.transaction()
on_each?(args..., tx)
fn.apply(tx, args).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) ->
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()
).on("error", (m) =>
@assert false, pretty_json m
).request()
), -> 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.passphrase
# 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)
constructor: (declaration, @assert, @remote, @config) ->
@declaration = declaration
@accounts = {} # {$account_id : $xrp_amt}
@trusts_by_ci = {} # {$account_id : {$currency/$issuer : $iou_amt}}
@ious = {} # {$account_id : {$currency/$issuer : $iou_amt}}
@offers_by_ci = {} # {$account_id : {offers: [], $currency/$issuer : {total: $iou_amt}}}
@reserves = {}
@xrp_payments = [] # {$account_id: []}
@trusts = [] # {$account_id: []}
@iou_payments = [] # {$account_id: []}
@offers = [] # {$account_id: []}
@ensure_config_has_test_accounts()
@compile_accounts_balances_and_implicit_trusts()
@compile_explicit_trusts()
@compile_offers()
@check_reserves()
@format_payments()
@format_trusts()
setup: (log, done) ->
LOG = (m) ->
self.what = m
log(m)
accounts = (k for k,ac of @accounts).sort()
@remote.set_account_seq(seq, 1) for seq in accounts.concat 'root' # <--
accounts_apply_arguments = ([ac] for ac in @accounts)
self = this
async.waterfall [
(cb) ->
self.transactor(
Transaction::payment,
self.xrp_payments,
((src, dest, amt) ->
LOG("Account `#{src}` creating account `#{dest}` by
making payment of #{amt.to_text_full()}") ),
cb)
(cb) ->
self.transactor(
Transaction::ripple_line_set,
self.trusts,
((src, amt) ->
issuer = self.realias_issuer amt.issuer().to_json()
currency = amt.currency().to_json()
LOG("Account `#{src}` trusts account `#{issuer}` for
#{amt.to_text()} #{currency}") ),
cb)
(cb) ->
self.transactor(
Transaction::payment,
self.iou_payments,
((src, dest, amt, tx) ->
LOG("Account `#{src}` is making a payment of #{amt.to_text_full()}
to `#{dest}`") ),
cb)
(cb) ->
self.transactor(
Transaction::offer_create,
self.offers,
((src, pays, gets) ->
LOG("Account `#{src}` is selling #{gets.to_text_full()}
for #{pays.to_text_full()}")),
cb)
(cb) ->
testutils.ledger_close self.remote, cb
(cb) ->
self.requester(Remote::request_account_lines, accounts_apply_arguments,
((acc) ->
LOG("Checking account_lines for #{acc}")),
cb)
(cb) ->
self.requester(Remote::request_account_info, accounts_apply_arguments,
((acc) ->
LOG("Checking account_info for #{acc}")),
cb)
], (error) ->
assert !error,
"There was an error @ #{self.what}"
done()

View File

@@ -1 +1,5 @@
--reporter spec --ui tdd --timeout 10000 --slow 600
--reporter spec
--compilers coffee:coffee-script
--ui tdd
--timeout 10000
--slow 600

673
test/new-path-test.coffee Normal file
View File

@@ -0,0 +1,673 @@
################################### REQUIRES ###################################
extend = require 'extend'
fs = require 'fs'
async = require 'async'
deep_eq = require 'deep-equal'
{Amount
Remote
Seed
Base
Transaction
PathFind
sjcl
UInt160} = require 'ripple-lib'
testutils = require './testutils'
{Server} = require './server'
{LedgerState, TestAccount} = require './ledger-state'
{test_accounts} = require './random-test-addresses'
simple_assert = require 'assert'
#################################### README ####################################
"""
The tests are written in a declarative style:
Each case has an entry in the `path_finding_cases` object
The key translates to a `suite(key, {...})`
The `{...}` passed in is compiled into a setup/teardown for the `ledger` and
into a bunch of `test` invokations for the `paths_expected`
- aliases are used throughout for easier reading
- test account addresses will be created `on the fly`
no need to declare in testconfig.js
debugged responses from the server substitute addresses for aliases
- The fixtures are setup just once for each ledger, multiple path finding
tests can be executed
- `paths_expected` top level keys are group names
2nd level keys are path test declarations
test declaration keys can be suffixed meaningfully with
`_skip`
`_only`
test declaration values can set
debug: true
Will dump the path declaration and
translated request and subsequent response
- hops in `alternatives[*][paths][*]` can be written in shorthand
eg.
ABC/G3|G3
get `ABC/G3` through `G3`
ABC/M1|M1
get `ABC/M1` through `M1`
XRP|$
get `XRP` through `$`
$ signifies an order book rather than account
------------------------------------------------------------------------------
Tests can be written in the 'path-tests.json' file in same directory # <--
------------------------------------------------------------------------------
"""
#################################### HELPERS ###################################
assert = simple_assert
refute = (cond, msg) -> assert(!cond, msg)
prettyj = pretty_json = (v) -> JSON.stringify(v, undefined, 2)
propagater = (done) ->
(f) ->
->
return if done.aborted
try
f(arguments...)
catch e
done.aborted = true
throw e
assert_match = (o, key_vals, message) ->
"""
assert_match path[i], matcher,
"alternative[#{ai}].paths[#{pi}]"
"""
for k,v of key_vals
assert.equal o[k], v, message
#################################### CONFIG ####################################
config = testutils.init_config()
############################### ALTERNATIVES TEST ##############################
expand_alternative = (alt) ->
"""
Make explicit the currency and issuer in each hop in paths_computed
"""
amt = Amount.from_json(alt.source_amount)
for path in alt.paths_computed
prev_issuer = amt.issuer().to_json()
prev_currency = amt.currency().to_json()
for hop, hop_i in path
if not hop.currency?
hop.currency = prev_currency
if not hop.issuer? and hop.currency != 'XRP'
if hop.account?
hop.issuer = hop.account
else
hop.issuer = prev_issuer
if hop.type & 0x10
prev_currency = hop.currency
if hop.type & 0x20
prev_issuer = hop.issuer
else if hop.account?
prev_issuer = hop.account
return alt
create_shorthand = (alternatives) ->
"""
Convert explicit paths_computed into the format used by `paths_expected`
These can be pasted in as the basis of tests.
"""
shorthand = []
for alt in alternatives
short_alt = {}
shorthand.push short_alt
amt = Amount.from_json alt.source_amount
if amt.is_native()
short_alt.amount = amt.to_human()
if not (~short_alt.amount.search('.'))
short_alt.amount = short_alt.amount + '.0'
else
short_alt.amount = amt.to_text_full()
short_alt.paths = []
for path in alt.paths_computed
short_path = []
short_alt.paths.push short_path
for node in path
hop = node.currency
hop = "#{hop}/#{node.issuer}" if node.issuer?
hop = "#{hop}|#{if node.account? then node.account else "$"}"
short_path.push hop
return shorthand
ensure_list = (v) ->
if Array.isArray(v)
v
else
[v]
test_alternatives_factory = (realias_pp, realias_text) ->
"""
We are using a factory to create `test_alternatives` because it needs the
per ledger `realias_*` functions
"""
hop_matcher = (decl_hop) ->
[ci, f] = decl_hop.split('|')
if not f?
throw new Error("No `|` in #{decl_hop}")
[c, i] = ci.split('/')
is_account = if f == '$' then false else true
matcher = currency: c
matcher.issuer = i if i?
matcher.account = f if is_account
matcher
match_path = (test, path, ai, pi) ->
test = (hop_matcher(hop) for hop in test)
assert.equal path.length, test.length,
"alternative[#{ai}] path[#{pi}] expecting #{test.length} hops"
for matcher, i in test
assert_match path[i], matcher,
"alternative[#{ai}].paths[#{pi}]"
return
simple_match_path = (test, path, ai, pi) ->
"""
@test
A shorthand specified path
@path
A path as returned by the server with `expand_alternative` done
so issuer and currency are always stated.
"""
test = (hop_matcher(hop) for hop in test)
return false if not test.length == path.length
for matcher, i in test
for k, v of matcher
return false if not path[i]?
if path[i][k] != v
return false
true
amounts = ->
(Amount.from_json a for a in arguments)
amounts_text = ->
(realias_text a.to_text_full() for a in arguments)
check_for_no_redundant_paths = (alternatives) ->
for alt, i in alternatives
existing_paths = []
for path in alt.paths_computed
for existing in existing_paths
assert !(deep_eq path, existing),
"Duplicate path in alternatives[#{i}]\n"+
"#{realias_pp alternatives[0]}"
existing_paths.push path
return
test_alternatives = (test, actual, error_context) ->
"""
@test
alternatives in shorthand format
@actual
alternatives as returned in a `path_find` response
@error_context
a function providing a string with extra context to provide to assertion
messages
"""
check_for_no_redundant_paths actual
for t, ti in ensure_list(test)
a = actual[ti]
[t_amt, a_amt] = amounts(t.amount, a.source_amount)
[t_amt_txt, a_amt_txt] = amounts_text(t_amt, a_amt)
# console.log typeof t_amt
assert t_amt.equals(a_amt),
"Expecting alternative[#{ti}].amount: "+
"#{t_amt_txt} == #{a_amt_txt}"
t_paths = ensure_list(t.paths)
tn = t_paths.length
an = a.paths_computed.length
assert.equal tn, an, "Different number of paths specified for alternative[#{ti}]"+
", expected: #{prettyj t_paths}, "+
"actual(shorthand): #{prettyj create_shorthand actual}"+
"actual(verbose): #{prettyj a.paths_computed}"+
error_context()
for p, i in t_paths
matched = false
for m in a.paths_computed
if simple_match_path(p, m, ti, i)
matched = true
break
assert matched, "Can't find a match for path[#{i}]: #{prettyj p} "+
"amongst #{prettyj create_shorthand [a]}"+
error_context()
return
################################################################################
create_path_test = (pth) ->
return (done) ->
propagates = propagater done
self = this
WHAT = self.log_what
ledger = self.ledger
test_alternatives = test_alternatives_factory ledger.pretty_json.bind(ledger),
ledger.realias_issuer
WHAT "#{pth.title}: #{pth.src} sending #{pth.dst}, "+
"#{pth.send}, via #{pth.via}"
one_message = (f) ->
self.remote._servers[0].once 'before_send_message_for_non_mutators', f
sent = "TODO: need to patch ripple-lib"
one_message (m) -> sent = m
error_info = (m, more) ->
info = path_expected: pth, path_find_request: sent, path_find_updates: m
extend(info, more) if more?
ledger.pretty_json(info)
assert Amount.from_json(pth.send).is_valid(),
"#{pth.send} is not valid Amount"
_src = UInt160.json_rewrite(pth.src)
_dst = UInt160.json_rewrite(pth.dst)
_amt = Amount.from_json(pth.send)
# self.server.clear_logs() "TODO: need to patch ripple-lib"
pf = self.remote.path_find(_src, _dst, _amt, [{currency: pth.via}])
updates = 0
max_seen = 0
messages = {}
pf.on "error", propagates (m) -> # <--
assert false, "fail (error): #{error_info(m)}"
done()
pf.on "update", propagates (m) -> # <--
# TODO:hack:
expand_alternative alt for alt in m.alternatives
messages[if updates then "update-#{updates}" else 'initial-response'] = m
updates++
assert m.alternatives.length >= max_seen,
"Subsequent path_find update' should never have less " +
"alternatives:\n#{ledger.pretty_json messages}"
max_seen = m.alternatives.length
if updates == 2
testutils.ledger_close(self.remote, -> )
if updates == 3
# "TODO: need to patch ripple-lib"
# self.log_pre(self.server.get_logs(), "Server Logs")
if pth.do_send?
do_send( (ledger.pretty_json.bind ledger), WHAT, self.remote, pth,
messages['update-2'], done )
if pth.debug
console.log ledger.pretty_json(messages)
console.log error_info(m)
console.log ledger.pretty_json create_shorthand(m.alternatives)
if pth.alternatives?
# We realias before doing any comparisons
alts = ledger.realias_issuer(JSON.stringify(m.alternatives))
alts = JSON.parse(alts)
test = pth.alternatives
assert test.length == alts.length,
"Number of `alternatives` specified is different: "+
"#{error_info(m)}"
if test.length == alts.length
test_alternatives(pth.alternatives, alts, -> error_info(m))
if pth.n_alternatives?
assert pth.n_alternatives == m.alternatives.length,
"fail (wrong n_alternatives): #{error_info(m)}"
done() if not pth.do_send?
################################ SUITE CREATION ################################
skip_or_only = (title, test_or_suite) ->
endsWith = (s, suffix) ->
~s.indexOf(suffix, s.length - suffix.length)
if endsWith title, '_only'
test_or_suite.only
else if endsWith title, '_skip'
test_or_suite.skip
else
test_or_suite
gather_path_definers = (path_expected) ->
tests = []
for group, subgroup of path_expected
for title, path of subgroup
definer_factory = (group, title, path) ->
path.title = "#{[group, title].join('.')}"
test_func = skip_or_only path.title, test
->
test_func(path.title, create_path_test(path) )
tests.push definer_factory(group, title, path)
tests
suite_factory = (declaration) ->
->
context = null
suiteSetup (done) ->
context = @
@log_what = ->
testutils.build_setup().call @, ->
context.ledger = new LedgerState(declaration.ledger,
assert,
context.remote,
config)
context.ledger.setup(context.log_what, done)
suiteTeardown (done) ->
testutils.build_teardown().call context, done
for definer in gather_path_definers(declaration.paths_expected)
definer()
define_suites = (path_finding_cases) ->
for case_name, declaration of path_finding_cases
suite_func = skip_or_only case_name, suite
suite_func case_name, suite_factory(declaration)
############################## PATH FINDING CASES ##############################
# Later we reference A0, the `unknown account`, directly embedding the full
# address.
A0 = (new TestAccount('A0')).address
assert A0 == 'rBmhuVAvi372AerwzwERGjhLjqkMmAwxX'
path_finding_cases_string = fs.readFileSync(__dirname + "/path-tests.json")
path_finding_cases = JSON.parse path_finding_cases_string
# 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.
extend path_finding_cases,
"Path Tests (Bitstamp + SnapSwap account holders | liquidity provider with no offers)":
ledger:
accounts:
G1BS:
balance: ["1000.0"]
G2SW:
balance: ["1000.0"]
A1:
balance: ["1000.0", "1000/HKD/G1BS"]
trusts: ["2000/HKD/G1BS"]
A2:
balance: ["1000.0", "1000/HKD/G2SW"]
trusts: ["2000/HKD/G2SW"]
M1:
# SnapSwap wants to be able to set trust line quality settings so they
# can charge a fee when transactions ripple across. Liquitidy
# provider, via trusting/holding both accounts
balance: ["11000.0",
"1200/HKD/G1BS",
"5000/HKD/G2SW"
]
trusts: ["100000/HKD/G1BS", "100000/HKD/G2SW"]
# We haven't got ANY offers
paths_expected: {
BS:
P1:
debug: false
src: "A1", dst: "A2", send: "10/HKD/A2", via: "HKD"
n_alternatives: 1
P2:
debug: false
src: "A2", dst: "A1", send: "10/HKD/A1", via: "HKD"
n_alternatives: 1
P3:
debug: false
src: "G1BS", dst: "A2", send: "10/HKD/A2", via: "HKD"
alternatives: [
amount: "10/HKD/G1BS",
paths: [["HKD/M1|M1", "HKD/G2SW|G2SW"]]
]
P5:
debug: false
src: "M1",
send: "10/HKD/M1",
dst: "G1BS",
via: "HKD"
P4:
debug: false
src: "G2SW", send: "10/HKD/A1", dst: "A1", via: "HKD"
alternatives: [
amount: "10/HKD/G2SW",
paths: [["HKD/M1|M1", "HKD/G1BS|G1BS"]]
]
}
"Path Tests #4 (non-XRP to non-XRP, same currency)": {
ledger:
accounts:
G1: balance: ["1000.0"]
G2: balance: ["1000.0"]
G3: balance: ["1000.0"]
G4: balance: ["1000.0"]
A1:
balance: ["1000.0", "1000/HKD/G1"]
trusts: ["2000/HKD/G1"]
A2:
balance: ["1000.0", "1000/HKD/G2"]
trusts: ["2000/HKD/G2"]
A3:
balance: ["1000.0", "1000/HKD/G1"]
trusts: ["2000/HKD/G1"]
A4:
balance: ["10000.0"]
M1:
balance: ["11000.0", "1200/HKD/G1", "5000/HKD/G2"]
trusts: ["100000/HKD/G1", "100000/HKD/G2"]
offers: [
["1000/HKD/G1", "1000/HKD/G2"]
]
M2:
balance: ["11000.0", "1200/HKD/G1", "5000/HKD/G2"]
trusts: ["100000/HKD/G1", "100000/HKD/G2"]
offers: [
["10000.0", "1000/HKD/G2"]
["1000/HKD/G1", "10000.0"]
]
paths_expected: {
T4:
"A) Borrow or repay":
comment: 'Source -> Destination (repay source issuer)'
src: "A1", send: "10/HKD/G1", dst: "G1", via: "HKD"
alternatives: [amount: "10/HKD/A1", paths: []]
"A2) Borrow or repay":
comment: 'Source -> Destination (repay destination issuer)'
src: "A1", send: "10/HKD/A1", dst: "G1", via: "HKD"
alternatives: [amount: "10/HKD/A1", paths: []]
"B) Common gateway":
comment: 'Source -> AC -> Destination'
src: "A1", send: "10/HKD/A3", dst: "A3", via: "HKD"
alternatives: [amount: "10/HKD/A1", paths: [["HKD/G1|G1"]]]
"C) Gateway to gateway":
comment: 'Source -> OB -> Destination'
src: "G1", send: "10/HKD/G2", dst: "G2", via: "HKD"
debug: false
alternatives: [
amount: "10/HKD/G1"
paths: [["HKD/M2|M2"],
["HKD/M1|M1"],
["HKD/G2|$"]
["XRP|$", "HKD/G2|$"]
]
]
"D) User to unlinked gateway via order book":
comment: 'Source -> AC -> OB -> Destination'
src: "A1", send: "10/HKD/G2", dst: "G2", via: "HKD"
debug: false
alternatives: [
amount: "10/HKD/A1"
paths: [
["HKD/G1|G1", "HKD/G2|$"], # <--
["HKD/G1|G1", "HKD/M2|M2"],
["HKD/G1|G1", "HKD/M1|M1"],
["HKD/G1|G1", "XRP|$", "HKD/G2|$"]
]
]
"I4) XRP bridge":
comment: 'Source -> AC -> OB to XRP -> OB from XRP -> AC -> Destination'
src: "A1", send: "10/HKD/A2", dst: "A2", via: "HKD"
debug: false
alternatives: [
amount: "10/HKD/A1",
paths: [
# Focus
["HKD/G1|G1", "HKD/G2|$", "HKD/G2|G2" ],
["HKD/G1|G1", "XRP|$", "HKD/G2|$", "HKD/G2|G2"], # <--
# Incidental
["HKD/G1|G1", "HKD/M1|M1", "HKD/G2|G2"],
["HKD/G1|G1", "HKD/M2|M2", "HKD/G2|G2"]
]
]
}
},
"Path Tests #2 (non-XRP to non-XRP, same currency)": {
ledger:
accounts:
G1: balance: ["1000.0"]
G2: balance: ["1000.0"]
A1:
balance: ["1000.0", "1000/HKD/G1"]
trusts: ["2000/HKD/G1"]
A2:
balance: ["1000.0", "1000/HKD/G2"]
trusts: ["2000/HKD/G2"]
A3:
balance: ["1000.0"]
trusts: ["2000/HKD/A2"]
M1:
balance: ["11000.0", "5000/HKD/G1", "5000/HKD/G2"]
trusts: ["100000/HKD/G1", "100000/HKD/G2"]
offers: [
["1000/HKD/G1", "1000/HKD/G2"]
# ["2000/HKD/G2", "2000/HKD/G1"]
# ["2000/HKD/M1", "2000/HKD/G1"]
# ["100.0", "1000/HKD/G1"]
# ["1000/HKD/G1", "100.0"]
]
paths_expected: {
T4:
"E) Gateway to user":
ledger: false
comment: 'Source -> OB -> AC -> Destination'
# comment: 'Gateway -> OB -> Gateway 2 -> User'
src: "G1", send: "10/HKD/A2", dst: "A2", via: "HKD"
debug: false
alternatives: [
amount: "10/HKD/G1"
paths: [
["HKD/G2|$", "HKD/G2|G2"],
["HKD/M1|M1", "HKD/G2|G2"]
]
]
"F) Different gateways, ripple _skip":
comment: 'Source -> AC -> AC -> Destination'
"G) Different users of different gateways, ripple _skip":
comment: 'Source -> AC -> AC -> AC -> Destination'
"H) Different gateways, order book _skip":
comment: 'Source -> AC -> OB -> AC -> Destination'
"I1) XRP bridge _skip":
comment: 'Source -> OB to XRP -> OB from XRP -> Destination'
src: "A4", send: "10/HKD/G2", dst: "G2", via: "XRP"
debug: true
"I2) XRP bridge _skip":
comment: 'Source -> AC -> OB to XRP -> OB from XRP -> Destination'
"I3) XRP bridge _skip":
comment: 'Source -> OB to XRP -> OB from XRP -> AC -> Destination'
}
}
################################# DEFINE SUITES ################################
define_suites(path_finding_cases)

88
test/path-tests.json Normal file
View File

@@ -0,0 +1,88 @@
{
"Path Tests #1 (XRP -> XRP) and #2 (XRP -> IOU)": {
"ledger": {"accounts": {"A1": {"balance": ["100000.0",
"3500/XYZ/G1",
"1200/ABC/G3"],
"trusts": ["5000/XYZ/G1",
"5000/ABC/G3"]},
"A2": {"balance": ["10000.0"],
"trusts": ["5000/XYZ/G2",
"5000/ABC/G3"]},
"A3": {"balance": ["1000.0"],
"trusts": ["1000/ABC/A2"]},
"G1": {"balance": ["1000.0"]},
"G2": {"balance": ["1000.0"]},
"G3": {"balance": ["1000.0"]},
"M1": {"balance": ["1000.0",
"25000/XYZ/G2",
"25000/ABC/G3"],
"offers": [["1000/XYZ/G1",
"1000/XYZ/G2"],
["10000.0",
"1000/ABC/G3"]],
"trusts": ["100000/XYZ/G1",
"100000/ABC/G3",
"100000/XYZ/G2"]}}},
"paths_expected": {"T1": {"A1": {"n_alternatives": 0,
"src": "A1",
"send": "10.0",
"dst": "A2",
"via": "XRP"},
"A2": {"comment": "Send to non existing account",
"src": "A1",
"send_comment": "malformed error not great for 10.0 amount",
"send": "200.0",
"dst": "rBmhuVAvi372AerwzwERGjhLjqkMmAwxX",
"via": "XRP",
"n_alternatives": 0}},
"T2": {"A": {"alternatives": [{"amount": "100.0",
"paths": [
["ABC/G3|$"]
]}],
"src": "A2",
"send": "10/ABC/G3",
"dst": "G3",
"via": "XRP",
"debug": 0,
"n_alternatives": 1},
"B": {"alternatives": [{"amount": "10.0",
"paths": [["ABC/G3|$",
"ABC/G3|G3"]]}],
"src": "A1",
"send": "1/ABC/A2",
"dst": "A2",
"via": "XRP",
"n_alternatives": 1},
"C": {"alternatives": [{"amount": "10.0",
"paths": [["ABC/G3|$",
"ABC/G3|G3",
"ABC/A2|A2"]]}],
"src": "A1",
"send": "1/ABC/A3",
"dst": "A3",
"via": "XRP",
"n_alternatives": 1}}}},
"Path Tests #3 (non-XRP to XRP)": {
"ledger": {"accounts": {"A1": {"balance": ["1000.0",
"1000/ABC/G3"]},
"A2": {"balance": ["1000.0",
"1000/ABC/G3"]},
"G3": {"balance": ["1000.0"]},
"M1": {"balance": ["11000.0",
"1200/ABC/G3"],
"offers": [["1000/ABC/G3",
"10000.0"]],
"trusts": ["100000/ABC/G3"]}}},
"paths_expected": {"T3": {"A": {"alternatives": [{"amount": "1/ABC/A1",
"paths": [["ABC/G3|G3",
"XRP|$"]]}],
"src": "A1",
"dst": "A2",
"debug":false,
"send": "10.0",
"via": "ABC"}}}}
}

View File

@@ -0,0 +1,153 @@
exports.test_accounts = [
'rBmhuVAvi372AerwzwERGjhLjqkMmAwxX',
'r4nmQNH4Fhjfh6cHDbvVSsBv7KySbj4cBf',
'rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD',
'rrnsYgWn13Z28GtRgznrSUsLfMkvsXCZSu',
'rJsaPnGdeo7BhMnHjuc3n44Mf7Ra1qkSVJ',
'rnYDWQaRdMb5neCGgvFfhw3MBoxmv5LtfH',
'r31PEiKfa3y6xTi7uBcSp7F3nDLvVMmqyi',
'rfM5xD2CY6XB8o1WsWoJ3ZHGkbHU4NYXr',
'r9MB1RNWZChfV3YdLrB3Rm5AoMULewDtiu',
'rH15iZg9KFSi7d1usvcsPerUtg7dhpMbk4',
'rGWYwGaczQWiduWkccFZKXfp5nDRPqNBNS',
'rBU1EP5oMwKxWr1gZnNe7K8GouQTBhzUKs',
'rwiTxuknPNeLDYHHLgajRVetKEEwkYhTaQ',
'rEbq9pWn2knXFTjjuoNNrKgQeGxhmispMi',
'rJfBCsnwSHXjTJ4GH5Ax6Kyw48X977hqyq',
'rado7qRcvPpS8ZL8SNg4SG8kBNksHyqoRa',
'rNgfurDhqvfsVzLr5ZGB3dJysJhRkvJ79F',
'rBtVTnNgX3uR3kyfVyaQ6hjZTdk42ay9Z3',
'rND1XyLAU9G2ydhUgmRo4i2kdrSKgYZc31',
'rHd21p9Gb834Ri4pzRzGFJ7PjRzymWuBWu',
'rhfvFTgpPzCvFPgbA9Nvz42mX92U5ak92m',
'rByT8y4BUX1Kcc6xotEabCgwc5PGsbTfSv',
'rPBrMbL7uGuteU49b2ibcEBSztoWPX4srr',
'rQGRcZn2RyXJL3s7Dfqqzc96Juc7j9j6i9',
'rwZrLewGghBMj29kFq7TPw9h9p5eAQ1LUp',
'rM9qHXk5uifboWrpu9Wte6Gjzbe974nZ4z',
'rBt69hdwMeBgmAtM9YwuFAKxjMqgaBLf3F',
'rHpcrpggafr5NNn7mafPhaeB8PMYKXGahp',
'rsMKP5MSoyve54o7LgwnZzFfGKzAK8SE5F',
'rfN3ccsNxPt41MjHNZRk7ek7q4TpPLqUzL',
'rDRWocPjBdhKZSWZezbsnwNpALcJ6GqSGf',
'rsmJ4tEMWpcK2qcEMp9uoUv4Ht5Nd6cGTV',
'rsxWsnMVRnFozrKJV2VZ1SG6UNbEeHYu16',
'r4KoiD6MpaQNPzBka3FRLREkx6EZFwynY4',
'rUdovjorVqxyemu5jbpfqA6DYDLdD4eYcj',
'r4MrNttmbdiJ7DjWh1MCKW3Kh7kfML46TA',
'rndYw73Btcm9Pv9gssZY1S9UcDUPLnpip7',
'rh8MnoZmAeWyLx7X8bJZqyjZ48mv1og5PS',
'rBoJvU7pcvoy5hjDMMTDNVG4YG85Ed3MEq',
'rs4f1BwdNgXAHWLT8rZgW2T1RKSBNY4iDz',
'rEmhxShqw42EPm7bY7df5uQySZBkQWnqae',
'rNerRdGnbZP6wej22zBdoTUfQKWoMDTH7d',
'rDyXvd2WFALJovh76uLe5kUrJ7QLpgmQYE',
'rUVi1L28AsCvieXP5pMqPHA9WAfsvCDUjU',
'rscuoJN9um2VM4xVv386X5T9APtExFKsbB',
'raeeyPs6g5xQn5jyNQbCZ6QeLrqu3FrFvb',
'r9UqovJD979WTfNEWXxDU2CVj3K1yo2mqG',
'rfRjsAqM1MEuSbWzuLcD6EhSZazgqvZSjy',
'rUL4CAxmfpNqDXsQTPCK9ZJ8zHqhUvDWfw',
'rP6ZRDFZxjQqeAgdBh1YQSQjWNSASpCL7N',
'rsV4AtAqsdyRyZ8s4kaWbM21EPwY5fonx5',
'rHaKEMyJErGY6VaKuTj16fSheTp4BRpWG1',
'rELHJtahsRpSiSj1nfkY5yKRHCCyRgynw4',
'rLYtaGnw4xK86J6mTsLfayyREoYaPPr8Cj',
'rD5pAYUfZypmJRSrJnBy3pYo5ApHqw5Jt5',
'rfYQrqwNXoA8e2gBDmiHAJAMYrASdQvqDm',
'rESt1CB9Sqaj8PYj8SV9x76iwMGBFPzLHb',
'rHZWAXh3NdQbyksKzDRLeP9ui32TcqssHZ',
'rK9iNjw5SozqKj5zNervwQQTLAgu8V813j',
'rUjpFBSmZ8F6cP16VxqpdAXCVCW3rBSZyn',
'raPib2vNQAjhh47fVQ7PswKaX1daNBSs2G',
'rwhuqz7FppLNvLWdxs7TLLW9UDVztFbw9z',
'rJYRe27KXWTjs4P3uu1d4x58Pk5Y13DbUg',
'rLFxCuE2GHq38wFUHpswgHJAcz6EUhPimC',
'rAaQrzi5satsth174EogwdtdxLZRW5n1h',
'rB18Rxdv1aPYtf9nDFpNPJ2HA5BBAqmyoG',
'rDSaTM6nCSrc1vH8pPcTAwQpmvb9Y6M2gw',
'rpmeCBJUpp9ij1nRM23tRGesWjY7chSHqs',
'rwQz7ZkCGdQt7iiuWC3EpbbKwWdL7uLT9C',
'rULwRizwxjBwDjcaA44Tbh9MjM5TZFTcLj',
'rGMZBEGHbSoegfvqXC2ajXe7RM2Z1LK65N',
'rGyFSppE8G7cELAdBU5yL7kWodbw29YdpN',
'rJYN3qZsjWhH6bzXX6ZHMZewkMPHeEyGNb',
'rEgZVpVSs75eEh6KM4HvcvG64p2TZBL4DC',
'rBRfZaqSAkXZYQWBfoy4sN2Q7zZHVGiGwU',
'rwg1DRGXLTzQpoWtS35mDowN4PSFQ732eQ',
'rKZvkY3T6ahhqkWLTQDSdB1MTKFbbnkqBX',
'rGXgpwvaAZ7rBmoSKFUrd83N7WN3Lm4vuX',
'rhCMsQ3SJa5Wb4AvBe27hxBQaQQjDG1LG4',
'rNtvcU3ePYpZnuYKG77pjRxtKJJ1yrutbm',
'rsfK2cTikveAeyvSG8F62c4VFUvZBsH5Rf',
'rJT2LrXe7hH1pMEnhEkCMznwtgKuYJS7uz',
'rE4Fi4GVjo9NY2g6MtMbitjenUZ21zFoSG',
'rp39zV6AFPRJ7yrrL1PSPC1s3oKM2uv2iW',
'raCoTW3mhdK6WGUZUSEuvbFM34CSGRRHut',
'rKZD9yCV7XAgKq3Rj3a5DmqHHkHBd3ZYao',
'rfKtpLEQz8bGCVtQsEzD8cJKeo6AW6J2pD',
'rJqNyWJ3rovwkWFdwPhCc6m3jpFogGzRr9',
'r41fiShunXNNgJjTjqU9whsjnSYU1BXsjY',
'r3uHcWgsNwowCBGF5rhCP5dfdjpYmByBbJ',
'r4GZm8WnwX5E9cr8uGiLX42y7KN2NaLqYn',
'rKBVsMWErdH443FUkaT799CRVyY9XnVyCK',
'razu52accAWWHjxhWEHXNLHWCDhhs1L79p',
'rHaL5e3niiikv6KJG4UARqpcjyD5tKNgyV',
'rMfcPdGcB5y9zEYw91t3QG2Qj1Z7tqms3S',
'r3mqtPNiwkLKisvbnFjM9eC8Rm9JUEXLMD',
'rKJGLaJr5SFjZ43BkDeqWKWAtGy1qAAkPf',
'rM2zQeTDrt6sMM936Gxh263t1qx3hEb7XK',
'rGtqoQJGe4zWenC3yWH9pByTAsGPcjmTU2',
'raNUsR5m8jsmb9o9mpNZGGyV86aZUYPunr',
'rManQrKu85ezooZ11UmXbxejw5EYHqiUZm',
'rKrjCBtwnhQeYkJKrVjDA5CaR5W9NPN2Bb',
'rcwyaWqsvGXMRyVW2KHnGUV2MhJVhVx2p',
'rLozhAmzcwtEgr1bA28GUh2kbf5kFBMhph',
'rJqt8XVcGdpfjZmujoTc6ArHQdZZVhADgM',
'rPthXZ93cGAtHJrnAF58vZz6E1js8o6fVf',
'rL1LH68PG93BuAeLsiM4cxeBtFmxsoGhRB',
'rndqHX6xLknzbgmQPXL4DvhNLRANYf8cDA',
'rNEnd2tV3Z1aL5kQmShmHGM3uciSJ4jakF',
'rGqPPWnLVWDwWXf495qdxp4um1BTw8TBh5',
'rhEDG4mzWGXjjfQp4WMCpZPBDyug5ays9e',
'raR6MJ2dYxj1PECUKnLbeamM1k8FnYsY34',
'rDcC1S6TRNgTMTgpuSzhAZdTdQJ5EHVf8X',
'rLHtg3bWtMKNDYXydd7frGB5pvFbtqzTjg',
'rnHhh7qXu2trXuC8aD3Zm7gvM9YetRkEDB',
'rfwwUrJztrR7PeKzELEoC1cjcBpsqHmxws',
'rw4KzQgZwPJDYX4wHc3NgsLjdR12JGAKsF',
'rPCbN5kfEjNgP7TBN3VDJf8eUhv1zCreRL',
'r32W6FTsguE3hMv3h79eoNVSJutsDDw6rH',
'rBSDvMzyxsea8Zgy8EfryZ7cc1QGrVp9Do',
'rfz1TPVJPQYbpSxi6oTiWyhNqy4J1Vz7VL',
'rKCyiG5sKxqmKoT2NGT9zS86gS26m9HrQa',
'rK7WRdHd6aNqCu2tNbebxYrQkP5u2DouS3',
'rLC3xWV4N3ohGxxzcueDrxPLHNqbXrmpUK',
'rDE2MwprS1vFvknSKLZJfmxKmbVuE2F85',
'r91o1Huejw2qqz37b22Ev8igM4V2Rog1jv',
'rEhGRU4P7pVjuxco5BgoXz1974QESKQ1Wy',
'raVZ3Uk3qmKqe5aV1ifQsRFL6gU7m25Y2L',
'r9p5buUwdMmZ5Um5uK5gC9piRd1fBzMPBN',
'r4ruPrbfwnkp5aDSNzQXnWcEK64hJGRnDG',
'rwsHCfEwi3PzsfcpcaWLbHXZ2zG5FtNX1J',
'r4DkAh7hbTX4E8JPp4kAWkQYazW6236dBB',
'rGZSqs5KHQKEJMCaDS3iyWagymixa9zuCv',
'r9Nn5Le1bourefaZJ79K2h5VnREFpTqjUw',
'rh7NSNpXR9mwWFF8uXvaWjcLVRPfxqQfM9',
'rpdxPDQ8mV8gadRcDBh7kNFj1mxYjfddh5',
'rMHxgnXgo68vDLUj327YtsHjQyiHGjeccj',
'raDd7xZ3RYvKcGiojy9h5UqFV7WULv626i',
'rEHzCw3nAuHj66C3xKnGFxV6zDBAssNdSY',
'rfvWW9qgyNKYVuJ212himJ7mt6fvkUaS7E',
'r9ctoegJfquoj1RHo1bxuW9MjWhbjHyZSA',
'rsNPHLq4CgxGoenX67jpuabKe8hx1QvLZk',
'rLZouhBz2CpT3ULfJjt193gsLNR4TxjdUS',
'rKuUjCThS3DBqfDAupxTvyhkWaskxHoWSP',
'rKwSstBmYbKFM3CFw8hvBqsU8pfcZMgNwA',
'rBN8iGgpbCc6KYNE36D67TeS6t1YXwmTQc',
'rGvBvCyqwn5kPuRUErGD7idtKRb7LtptrS',
'r3h89HbdaYJBVd1wfHV6GxDj6Pf2s5iXnF',
'rfbUVWFke9JzbK6BhkuayCgX2MVoJvgxBk',
'rwTXRBQvPKPMskjp9By1kLJ1pPdRAg77Rz',
'rw4ve76xnh8cPwVpE58i46s39MbQ7x1m5W',
'r32XzGdUP3GuNJXDGxFM2cYkw7K1KrwXdt' ]

View File

@@ -1,4 +1,4 @@
#!/bin/bash
mocha --ui tdd --reporter spec --timeout 10000 test/*-test.js
# flags set in mocha.opts
mocha test/*-test.{js,coffee}

View File

@@ -28,7 +28,7 @@ suite('WebSocket connection', function() {
}
});
test('WebSocket connect and disconnect', function() {
test('WebSocket connect and disconnect', function(done) {
var alpha = Remote.from_config("alpha");
alpha.on('connected', function () {