mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-19 10:35:50 +00:00
Ensure no offers cancel each other out
This commit is contained in:
committed by
Vinnie Falco
parent
bf1843be9e
commit
8d5378a2ca
@@ -2,16 +2,18 @@
|
||||
|
||||
# This gives coffee-script proper file/lines in the exceptions
|
||||
|
||||
async = require("async")
|
||||
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")
|
||||
{
|
||||
Amount
|
||||
Remote
|
||||
Seed
|
||||
Base
|
||||
Transaction
|
||||
sjcl
|
||||
} = require 'ripple-lib'
|
||||
{Server} = require './server'
|
||||
testutils = require './testutils'
|
||||
|
||||
#################################### HELPERS ###################################
|
||||
|
||||
@@ -21,14 +23,18 @@ 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()
|
||||
derive_pair: (passphrase) ->
|
||||
seed = Seed.from_json(passphrase)
|
||||
master_seed = seed.to_json()
|
||||
key_pair = seed.get_key()
|
||||
pubKey = sjcl.codec.hex.toBits key_pair.to_hex_pub()
|
||||
Base.encode_check(0,sjcl.codec.bytes.fromBits(@SHA256_RIPEMD160 pubKey))
|
||||
address = Base.encode_check(0,
|
||||
sjcl.codec.bytes.fromBits(@SHA256_RIPEMD160 pubKey))
|
||||
[address, master_seed, key_pair]
|
||||
|
||||
constructor: (passphrase) ->
|
||||
@passphrase = passphrase
|
||||
@address = @get_address(passphrase)
|
||||
@passphrase = passphrase
|
||||
[@address, @master_seed, @key_pair] = @derive_pair(passphrase)
|
||||
|
||||
############################# LEDGER STATE COMPILER ############################
|
||||
|
||||
@@ -46,25 +52,17 @@ exports.LedgerState = class LedgerState
|
||||
@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_json(amt_val)
|
||||
if not amt.is_valid()
|
||||
amt = Amount.from_human(amt_val)
|
||||
if not amt.is_valid()
|
||||
throw new Error()
|
||||
catch e
|
||||
amt = null
|
||||
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
|
||||
"#{currency}/#{issuer}"
|
||||
|
||||
apply: (context)->
|
||||
@create_accounts_by_issuing_xrp_from_root(context)
|
||||
@@ -112,6 +110,8 @@ exports.LedgerState = class LedgerState
|
||||
"Trust amount #{amt_val} specified for #{account_id} is not valid"
|
||||
@record_trust(account_id, amt, false)
|
||||
|
||||
undefined
|
||||
|
||||
compile_accounts_balances_and_implicit_trusts: ->
|
||||
for account_id, account of @declaration.accounts
|
||||
xrp_balance = null
|
||||
@@ -133,6 +133,8 @@ exports.LedgerState = class LedgerState
|
||||
@assert xrp_balance,
|
||||
"No XRP balanced declared for #{account_id}"
|
||||
|
||||
undefined
|
||||
|
||||
compile_offers: ->
|
||||
for account_id, account of @declaration.accounts
|
||||
if not account.offers?
|
||||
@@ -147,28 +149,31 @@ exports.LedgerState = class LedgerState
|
||||
@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, [])
|
||||
offers = @ensure(account_id, @offers_by_ci)
|
||||
offers = @ensure(account_id, @offers_by_ci)
|
||||
offers_all = @ensure('offers', offers, [])
|
||||
|
||||
if gets_amt.is_native()
|
||||
total = a_offers.xrp_total ?= new Amount.from_json('0')
|
||||
total = 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, {})
|
||||
|
||||
if key.split('/')[1] != account_id
|
||||
key_offers = @ensure(key, 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}"
|
||||
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
|
||||
key_offers.total = new_total
|
||||
|
||||
offers_all.push [pays_amt, gets_amt, splat...]
|
||||
|
||||
@@ -180,11 +185,13 @@ exports.LedgerState = class LedgerState
|
||||
@offers.push sliced
|
||||
# @offers[account_id] = obj.offers
|
||||
|
||||
@offers
|
||||
|
||||
base_reserve: ->
|
||||
@declaration.reserve?.base ? "50.0"
|
||||
@declaration.reserve?.base ? "200.0"
|
||||
|
||||
incr_reserve: ->
|
||||
@declaration.reserve?.base ? "12.5"
|
||||
@declaration.reserve?.base ? "50.0"
|
||||
|
||||
check_reserves: ->
|
||||
base_reserve_amt = @base_reserve()
|
||||
@@ -203,11 +210,11 @@ exports.LedgerState = class LedgerState
|
||||
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?
|
||||
offers = @offers_by_ci[account_id]
|
||||
if offers?
|
||||
if offers.xrp_total?
|
||||
total_needed = total_needed.add offers.xrp_total
|
||||
if offers.offers?
|
||||
owner_count += @offers_by_ci[account_id].offers.length
|
||||
|
||||
if @trusts_by_ci[account_id]?
|
||||
@@ -232,12 +239,16 @@ exports.LedgerState = class LedgerState
|
||||
src = @realias_issuer amt.issuer().to_json()
|
||||
dst = account_id
|
||||
@iou_payments.push [src, dst, amt]
|
||||
|
||||
undefined
|
||||
|
||||
format_trusts: ->
|
||||
for account_id, trusts of @trusts_by_ci
|
||||
for curr_issuer, amt of trusts
|
||||
@trusts.push [account_id, amt]
|
||||
|
||||
undefined
|
||||
|
||||
transactor: (fn, args_list, on_each, callback) ->
|
||||
if args_list.length == 0
|
||||
return callback()
|
||||
@@ -256,9 +267,10 @@ exports.LedgerState = class LedgerState
|
||||
|
||||
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}"
|
||||
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()
|
||||
@@ -270,7 +282,7 @@ exports.LedgerState = class LedgerState
|
||||
=> testutils.ledger_close @remote, ->
|
||||
)
|
||||
|
||||
requester: (fn, args_list, on_each, callback) ->
|
||||
requester: (fn, args_list, on_each, callback, on_results) ->
|
||||
if not callback?
|
||||
callback = on_each
|
||||
on_each = null
|
||||
@@ -282,12 +294,16 @@ exports.LedgerState = class LedgerState
|
||||
on_each?(args..., req)
|
||||
req.on("success", (m) =>
|
||||
if m.status?
|
||||
@assert m.status is "success", "requester failure: #{pretty_json m}"
|
||||
callback()
|
||||
@assert m.status is "success", "requester failure: #{@pretty_json m}"
|
||||
callback(null, m)
|
||||
).on("error", (m) =>
|
||||
@assert false, pretty_json m
|
||||
@assert false, @pretty_json m
|
||||
).request()
|
||||
), -> callback())
|
||||
),
|
||||
(error, results_list) ->
|
||||
on_results?(results_list);
|
||||
callback()
|
||||
)
|
||||
|
||||
ensure_config_has_test_accounts: ->
|
||||
for account of @declaration.accounts
|
||||
@@ -295,7 +311,7 @@ exports.LedgerState = class LedgerState
|
||||
acc = @config.accounts[account] = {}
|
||||
user = new TestAccount(account)
|
||||
acc.account = user.address
|
||||
acc.secret = user.passphrase
|
||||
acc.secret = user.master_seed
|
||||
# Index by nickname ...
|
||||
@remote.set_secret account, acc.secret
|
||||
# ... and by account ID
|
||||
@@ -333,7 +349,7 @@ exports.LedgerState = class LedgerState
|
||||
|
||||
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)
|
||||
accounts_apply_arguments = ([ac] for ac, _ of @accounts)
|
||||
self = this
|
||||
|
||||
async.waterfall [
|
||||
@@ -367,9 +383,10 @@ exports.LedgerState = class LedgerState
|
||||
self.transactor(
|
||||
Transaction::offer_create,
|
||||
self.offers,
|
||||
((src, pays, gets) ->
|
||||
LOG("Account `#{src}` is selling #{gets.to_text_full()}
|
||||
for #{pays.to_text_full()}")),
|
||||
((src, pays, gets, tx) ->
|
||||
tx.set_flags('Passive')
|
||||
LOG("Account `#{src}` is selling #{gets.to_text_full()}
|
||||
for #{pays.to_text_full()}")),
|
||||
cb)
|
||||
(cb) ->
|
||||
testutils.ledger_close self.remote, cb
|
||||
@@ -378,6 +395,31 @@ exports.LedgerState = class LedgerState
|
||||
((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) ->
|
||||
|
||||
@@ -74,6 +74,7 @@ The tests are written in a declarative style:
|
||||
#################################### HELPERS ###################################
|
||||
|
||||
assert = simple_assert
|
||||
|
||||
refute = (cond, msg) -> assert(!cond, msg)
|
||||
prettyj = pretty_json = (v) -> JSON.stringify(v, undefined, 2)
|
||||
|
||||
@@ -131,7 +132,7 @@ expand_alternative = (alt) ->
|
||||
prev_issuer = hop.issuer
|
||||
else if hop.account?
|
||||
prev_issuer = hop.account
|
||||
|
||||
|
||||
return alt
|
||||
|
||||
create_shorthand = (alternatives) ->
|
||||
@@ -268,7 +269,7 @@ test_alternatives_factory = (realias_pp, realias_text) ->
|
||||
[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}"
|
||||
@@ -300,8 +301,6 @@ test_alternatives_factory = (realias_pp, realias_text) ->
|
||||
|
||||
create_path_test = (pth) ->
|
||||
return (done) ->
|
||||
propagates = propagater done
|
||||
|
||||
self = this
|
||||
WHAT = self.log_what
|
||||
ledger = self.ledger
|
||||
@@ -319,7 +318,11 @@ create_path_test = (pth) ->
|
||||
one_message (m) -> sent = m
|
||||
|
||||
error_info = (m, more) ->
|
||||
info = path_expected: pth, path_find_request: sent, path_find_updates: m
|
||||
info =
|
||||
path_expected: pth,
|
||||
path_find_request: sent,
|
||||
path_find_updates: messages
|
||||
|
||||
extend(info, more) if more?
|
||||
ledger.pretty_json(info)
|
||||
|
||||
@@ -337,6 +340,8 @@ create_path_test = (pth) ->
|
||||
max_seen = 0
|
||||
messages = {}
|
||||
|
||||
propagates = propagater done
|
||||
|
||||
pf.on "error", propagates (m) -> # <--
|
||||
assert false, "fail (error): #{error_info(m)}"
|
||||
done()
|
||||
@@ -402,17 +407,16 @@ skip_or_only = (title, test_or_suite) ->
|
||||
else
|
||||
test_or_suite
|
||||
|
||||
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) )
|
||||
|
||||
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
|
||||
|
||||
@@ -425,9 +429,9 @@ suite_factory = (declaration) ->
|
||||
@log_what = ->
|
||||
|
||||
testutils.build_setup().call @, ->
|
||||
context.ledger = new LedgerState(declaration.ledger,
|
||||
assert,
|
||||
context.remote,
|
||||
context.ledger = new LedgerState(declaration.ledger,
|
||||
assert,
|
||||
context.remote,
|
||||
config)
|
||||
|
||||
context.ledger.setup(context.log_what, done)
|
||||
@@ -456,6 +460,142 @@ path_finding_cases = JSON.parse path_finding_cases_string
|
||||
# gateway and holds its currency, and a destination that trusts the other.
|
||||
|
||||
extend path_finding_cases,
|
||||
"CNY test":
|
||||
paths_expected:
|
||||
BS:
|
||||
P101: src: "SRC", dst: "GATEWAY_DST", send: "10.1/CNY/GATEWAY_DST", via: "XRP", n_alternatives: 1
|
||||
|
||||
ledger:
|
||||
accounts:
|
||||
SRC:
|
||||
balance: ["4999.999898"]
|
||||
trusts: []
|
||||
offers: []
|
||||
|
||||
GATEWAY_DST:
|
||||
balance: ["10846.168060"]
|
||||
trusts: []
|
||||
offers: []
|
||||
|
||||
MONEY_MAKER_1:
|
||||
balance: ["4291.430036"]
|
||||
trusts: []
|
||||
offers: []
|
||||
|
||||
MONEY_MAKER_2:
|
||||
balance: [
|
||||
"106839375770"
|
||||
"0.0000000003599/CNY/MONEY_MAKER_1"
|
||||
"137.6852546843001/CNY/GATEWAY_DST"
|
||||
]
|
||||
trusts: [
|
||||
"1001/CNY/MONEY_MAKER_1"
|
||||
"1001/CNY/GATEWAY_DST"
|
||||
]
|
||||
offers: [
|
||||
[
|
||||
"1000000"
|
||||
"1/CNY/GATEWAY_DST"
|
||||
# []
|
||||
]
|
||||
[
|
||||
"1/CNY/GATEWAY_DST"
|
||||
"1000000"
|
||||
# []
|
||||
]
|
||||
[
|
||||
"318000/CNY/GATEWAY_DST"
|
||||
"53000000000"
|
||||
# ["Sell"]
|
||||
]
|
||||
[
|
||||
"209000000"
|
||||
"4.18/CNY/MONEY_MAKER_2"
|
||||
# []
|
||||
]
|
||||
[
|
||||
"990000/CNY/MONEY_MAKER_1"
|
||||
"10000000000"
|
||||
# ["Sell"]
|
||||
]
|
||||
[
|
||||
"9990000/CNY/MONEY_MAKER_1"
|
||||
"10000000000"
|
||||
# ["Sell"]
|
||||
]
|
||||
[
|
||||
"8870000/CNY/GATEWAY_DST"
|
||||
"10000000000"
|
||||
# ["Sell"]
|
||||
]
|
||||
[
|
||||
"232000000"
|
||||
"5.568/CNY/MONEY_MAKER_2"
|
||||
# []
|
||||
]
|
||||
]
|
||||
|
||||
A1:
|
||||
balance: [
|
||||
# "240.997150"
|
||||
"1240.997150"
|
||||
"0.0000000119761/CNY/MONEY_MAKER_1"
|
||||
"33.047994/CNY/GATEWAY_DST"
|
||||
]
|
||||
trusts: [
|
||||
"1000000/CNY/MONEY_MAKER_1"
|
||||
"100000/USD/MONEY_MAKER_1"
|
||||
"10000/BTC/MONEY_MAKER_1"
|
||||
"1000/USD/GATEWAY_DST"
|
||||
"1000/CNY/GATEWAY_DST"
|
||||
]
|
||||
offers: []
|
||||
|
||||
A2:
|
||||
balance: [
|
||||
"14115.046893"
|
||||
"209.3081873019994/CNY/MONEY_MAKER_1"
|
||||
"694.6251706504019/CNY/GATEWAY_DST"
|
||||
]
|
||||
trusts: [
|
||||
"3000/CNY/MONEY_MAKER_1"
|
||||
"3000/CNY/GATEWAY_DST"
|
||||
]
|
||||
offers: [
|
||||
[
|
||||
"2000000000"
|
||||
"66.8/CNY/MONEY_MAKER_1"
|
||||
# []
|
||||
]
|
||||
[
|
||||
"1200000000"
|
||||
"42/CNY/GATEWAY_DST"
|
||||
# []
|
||||
]
|
||||
[
|
||||
"43.2/CNY/MONEY_MAKER_1"
|
||||
"900000000"
|
||||
# ["Sell"]
|
||||
]
|
||||
]
|
||||
|
||||
A3:
|
||||
balance: [
|
||||
"512087.883181"
|
||||
"23.617050013581/CNY/MONEY_MAKER_1"
|
||||
"70.999614649799/CNY/GATEWAY_DST"
|
||||
]
|
||||
trusts: [
|
||||
"10000/CNY/MONEY_MAKER_1"
|
||||
"10000/CNY/GATEWAY_DST"
|
||||
]
|
||||
offers: [[
|
||||
"2240/CNY/MONEY_MAKER_1"
|
||||
"50000000000"
|
||||
# ["Sell"]
|
||||
]]
|
||||
|
||||
|
||||
"Path Tests (Bitstamp + SnapSwap account holders | liquidity provider with no offers)":
|
||||
ledger:
|
||||
accounts:
|
||||
|
||||
Reference in New Issue
Block a user