Declarative path tests

This commit is contained in:
Nicholas Dudfield
2013-10-24 11:39:48 +07:00
committed by Vinnie Falco
parent 8201805b28
commit 3b19310252
7 changed files with 1310 additions and 4 deletions

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