mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Ported declarative path tests to mocha
This commit is contained in:
389
test/ledger-state.coffee
Normal file
389
test/ledger-state.coffee
Normal 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()
|
||||||
@@ -1 +1 @@
|
|||||||
--reporter spec --compilers coffee:coffee-script --ui tdd --timeout 10000 --slow 600
|
--reporter spec --compilers coffee:coffee-script --ui tdd --timeout 3000 --slow 600
|
||||||
677
test/new-path-test.coffee
Normal file
677
test/new-path-test.coffee
Normal file
@@ -0,0 +1,677 @@
|
|||||||
|
################################### 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) ->
|
||||||
|
"""
|
||||||
|
|
||||||
|
Params
|
||||||
|
@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) ->
|
||||||
|
"""
|
||||||
|
|
||||||
|
Params:
|
||||||
|
@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 = ->
|
||||||
|
|
||||||
|
@uniquely_gifted = 'yes'
|
||||||
|
|
||||||
|
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
88
test/path-tests.json
Normal 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"}}}}
|
||||||
|
}
|
||||||
153
test/random-test-addresses.coffee
Normal file
153
test/random-test-addresses.coffee
Normal 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' ]
|
||||||
Reference in New Issue
Block a user