Ensure no offers cancel each other out

This commit is contained in:
Nicholas Dudfield
2013-11-03 17:13:23 +07:00
committed by Vinnie Falco
parent bf1843be9e
commit 8d5378a2ca
2 changed files with 256 additions and 74 deletions

View File

@@ -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) ->

View File

@@ -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: