Compare commits

..

4 Commits

Author SHA1 Message Date
Nik Bougalis
70c2854f7c Set version to 0.27.3 2015-03-10 14:06:33 -07:00
David Schwartz
e9381ddeb2 Add "Default Ripple" account flag and associated logic:
AccountSet set/clear, asfDefaultRipple = 8

AccountRoot flag, lsfDefaultRipple = 0x00800000

In trustCreate, set no ripple flag if appropriate.

If an account does not have the default ripple flag set,
new ripple lines created as a result of its offers being
taken or people creating trust lines to it have no ripple
set by that account's side automatically

Trust lines can be deleted if the no ripple flag matches
its default setting based on the account's default ripple
setting.

Fix default no-rippling in integration tests.
2015-03-10 14:05:18 -07:00
Nik Bougalis
9cc8eec773 Set version to 0.27.2 2015-03-01 14:56:44 -08:00
Nik Bougalis
0b45535061 Calculate deep offer quality 2015-02-28 13:28:54 -08:00
17 changed files with 300 additions and 135 deletions

View File

@@ -1,5 +1,5 @@
Name: rippled
Version: 0.27.1
Version: 0.27.3
Release: 1%{?dist}
Summary: Ripple peer-to-peer network daemon

View File

@@ -2,13 +2,10 @@
"name": "rippled",
"version": "0.0.1",
"description": "Rippled Server",
"private": true,
"directories": {
"test": "test"
},
"dependencies": {
"ripple-lib": "0.8.2",
"async": "~0.2.9",
@@ -17,18 +14,16 @@
"deep-equal": "0.0.0"
},
"devDependencies": {
"assert-diff": "^1.0.1",
"coffee-script": "~1.6.3",
"mocha": "~1.13.0"
},
"scripts": {
"test": "mocha test/websocket-test.js test/server-test.js test/*-test.{js,coffee}"
},
"repository": {
"type": "git",
"url": "git://github.com/ripple/rippled.git"
},
"readmeFilename": "README.md"
}

View File

@@ -44,6 +44,7 @@ private:
uint256 m_dir;
uint256 m_index;
SLE::pointer m_entry;
Quality m_quality;
LedgerView&
view() const noexcept
@@ -67,10 +68,10 @@ public:
return m_index;
}
Quality const
Quality const&
quality() const noexcept
{
return Quality (getQuality (m_dir));
return m_quality;
}
SLE::pointer const&

View File

@@ -28,6 +28,7 @@ BookTip::BookTip (LedgerView& view, BookRef book)
, m_valid (false)
, m_book (getBookBase (book))
, m_end (getQualityNext (m_book))
, m_quality ()
{
}
@@ -46,31 +47,33 @@ BookTip::step ()
for(;;)
{
// See if there's an entry at or worse than current quality.
auto const page (
view().getNextLedgerIndex (m_book, m_end));
auto const first_page (view().getNextLedgerIndex (m_book, m_end));
if (page.isZero())
if (first_page.isZero())
return false;
unsigned int di (0);
SLE::pointer dir;
if (view().dirFirst (page, dir, di, m_index))
if (view().dirFirst (first_page, dir, di, m_index))
{
m_dir = dir->getIndex();
m_entry = view().entryCache (ltOFFER, m_index);
m_quality = Quality (getQuality (first_page));
m_valid = true;
// Next query should start before this directory
m_book = page;
m_book = first_page;
// The quality immediately before the next quality
--m_book;
break;
}
// There should never be an empty directory but just in case,
// we handle that case by advancing to the next directory.
m_book = page;
m_book = first_page;
}
return true;

View File

@@ -1342,6 +1342,12 @@ TER LedgerEntrySet::trustCreate (
const bool bSetDst = saLimit.getIssuer () == uDstAccountID;
const bool bSetHigh = bSrcHigh ^ bSetDst;
assert (sleAccount->getFieldAccount160 (sfAccount) ==
(bSetHigh ? uHighAccountID : uLowAccountID));
SLE::pointer slePeer = entryCache (ltACCOUNT_ROOT,
getAccountRootIndex (bSetHigh ? uLowAccountID : uHighAccountID));
assert (slePeer);
// Remember deletion hints.
sleRippleState->setFieldU64 (sfLowNode, uLowNode);
sleRippleState->setFieldU64 (sfHighNode, uHighNode);
@@ -1376,6 +1382,12 @@ TER LedgerEntrySet::trustCreate (
uFlags |= (!bSetHigh ? lsfLowFreeze : lsfHighFreeze);
}
if ((slePeer->getFlags() & lsfDefaultRipple) == 0)
{
// The other side's default is no rippling
uFlags |= (bSetHigh ? lsfLowNoRipple : lsfHighNoRipple);
}
sleRippleState->setFieldU32 (sfFlags, uFlags);
incrementOwnerCount (sleAccount);
@@ -1507,7 +1519,9 @@ TER LedgerEntrySet::rippleCredit (
// Sender is zero or negative.
&& (uFlags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
// Sender reserve is set.
&& !(uFlags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple))
&& static_cast <bool> (uFlags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
static_cast <bool> (entryCache (ltACCOUNT_ROOT,
getAccountRootIndex (uSenderID))->getFlags() & lsfDefaultRipple)
&& !(uFlags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze))
&& !sleRippleState->getFieldAmount (
!bSenderHigh ? sfLowLimit : sfHighLimit)

View File

@@ -168,6 +168,15 @@ public:
uFlagsOut &= ~lsfDisableMaster;
}
if (uSetFlag == asfDefaultRipple)
{
uFlagsOut |= lsfDefaultRipple;
}
else if (uClearFlag == asfDefaultRipple)
{
uFlagsOut &= ~lsfDefaultRipple;
}
if (uSetFlag == asfNoFreeze)
{
m_journal.trace << "Set NoFreeze flag";

View File

@@ -288,15 +288,17 @@ public:
if (QUALITY_ONE == uHighQualityOut) uHighQualityOut = 0;
bool const bLowDefRipple = sleLowAccount->getFlags() & lsfDefaultRipple;
bool const bHighDefRipple = sleHighAccount->getFlags() & lsfDefaultRipple;
bool const bLowReserveSet = uLowQualityIn || uLowQualityOut ||
(uFlagsOut & lsfLowNoRipple) ||
((uFlagsOut & lsfLowNoRipple) == 0) != bLowDefRipple ||
(uFlagsOut & lsfLowFreeze) ||
saLowLimit || saLowBalance > zero;
bool const bLowReserveClear = !bLowReserveSet;
bool const bHighReserveSet = uHighQualityIn || uHighQualityOut ||
(uFlagsOut & lsfHighNoRipple) ||
((uFlagsOut & lsfHighNoRipple) == 0) != bHighDefRipple ||
(uFlagsOut & lsfHighFreeze) ||
saHighLimit || saHighBalance > zero;
bool const bHighReserveClear = !bHighReserveSet;

View File

@@ -111,6 +111,7 @@ enum LedgerSpecificFlags
lsfDisableMaster = 0x00100000, // True, force regular key
lsfNoFreeze = 0x00200000, // True, cannot freeze ripple states
lsfGlobalFreeze = 0x00400000, // True, all assets frozen
lsfDefaultRipple = 0x00800000, // True, trust lines allow rippling by default
// ltOFFER
lsfPassive = 0x00010000,

View File

@@ -65,6 +65,7 @@ const std::uint32_t asfDisableMaster = 4;
const std::uint32_t asfAccountTxnID = 5;
const std::uint32_t asfNoFreeze = 6;
const std::uint32_t asfGlobalFreeze = 7;
const std::uint32_t asfDefaultRipple = 8;
// OfferCreate flags:
const std::uint32_t tfPassive = 0x00010000;

View File

@@ -35,7 +35,7 @@ char const* getRawVersionString ()
//
// The build version number (edit this for each release)
//
"0.27.1"
"0.27.3"
//
// Must follow the format described here:
//

View File

@@ -847,4 +847,4 @@ execute_if_enabled (suite, enforced) ->
remote.request_account_offers args, (err, res) ->
assert res.offers.length == 0
done()
done()

View File

@@ -497,8 +497,9 @@ exports.LedgerState = class LedgerState
add_transaction_fees: ->
extra_fees = {}
account_sets = ([k] for k,ac of @accounts)
fee = Amount.from_json(@remote.fee_cushion * 10)
for list in [@trusts, @iou_payments, @offers]
for list in [@trusts, @iou_payments, @offers, account_sets]
for [src, args...] in list
extra = extra_fees[src]
extra = if extra? then extra.add(fee) else fee
@@ -532,6 +533,13 @@ exports.LedgerState = class LedgerState
LOG("Account `#{src}` creating account `#{dest}` by "+
"making payment of #{amt.to_text_full()}") ),
cb)
(cb) ->
reqs.transactor(
Transaction::account_set,
accounts_apply_arguments,
((account, tx) ->
tx.tx_json.SetFlag = 8
), cb)
(cb) ->
reqs.transactor(
Transaction::ripple_line_set,

View File

@@ -326,3 +326,92 @@ suite('NoRipple', function() {
});
});
});
suite('Default ripple', function() {
var $ = { };
setup(function(done) {
testutils.build_setup().call($, done);
});
teardown(function(done) {
testutils.build_teardown().call($, done);
});
test('Set default ripple on account, check new trustline', function(done) {
var steps = [
function (callback) {
testutils.create_accounts(
$.remote,
'root',
'10000.0',
[ 'alice', 'bob' ],
{ default_rippling: false },
callback);
},
function (callback) {
var tx = $.remote.createTransaction('AccountSet', {
account: 'bob',
set_flag: 8
});
testutils.submit_transaction(tx, callback);
},
function (callback) {
var tx = $.remote.createTransaction('TrustSet', {
account: 'root',
limit: '100/USD/alice'
});
testutils.submit_transaction(tx, callback);
},
function (callback) {
var tx = $.remote.createTransaction('TrustSet', {
account: 'root',
limit: '100/USD/bob'
});
testutils.submit_transaction(tx, callback);
},
function (callback) {
$.remote.requestAccountLines({ account: 'root', peer: 'alice' }, function(err, m) {
assert.ifError(err);
assert(Array.isArray(m.lines));
assert(m.lines[0].no_ripple_peer,
'Trustline should have no_ripple_peer set');
callback();
});
},
function (callback) {
$.remote.requestAccountLines({ account: 'alice', peer: 'root' }, function(err, m) {
assert.ifError(err);
assert(Array.isArray(m.lines));
assert(m.lines[0].no_ripple,
'Trustline should have no_ripple set');
callback();
});
},
function (callback) {
$.remote.requestAccountLines({ account: 'root', peer: 'bob' }, function(err, m) {
assert.ifError(err);
assert(Array.isArray(m.lines));
assert(!m.lines[0].no_ripple,
'Trustline should not have no_ripple set');
callback();
});
},
function (callback) {
$.remote.requestAccountLines({ account: 'bob', peer: 'root' }, function(err, m) {
assert.ifError(err);
assert(Array.isArray(m.lines));
assert(!m.lines[0].no_ripple_peer,
'Trustline should not have no_ripple_peer set');
callback();
});
}
]
async.series(steps, function(error) {
assert(!error, error);
done();
});
});
});

View File

@@ -1909,7 +1909,7 @@ suite("Client Issue #535", function() {
var starting_xrp = $.amount_for({
ledger_entries: 1,
default_transactions: 2,
extra: '100.0'
extra: '100.1'
});
testutils.create_accounts($.remote, "root", starting_xrp, ["alice", "bob", "mtgox"], callback);
@@ -1938,12 +1938,16 @@ suite("Client Issue #535", function() {
$.remote.transaction()
.offer_create("alice", "100/XTS/mtgox", "100/XXX/mtgox")
.on('submitted', function (m) {
// console.log("proposed: offer_create: %s", json.stringify(m));
callback(m.engine_result !== 'tesSUCCESS');
.on('submitted', function(m) {
if (m.engine_result === 'tesSUCCESS') {
callback();
} else {
// console.log("proposed: %s", JSON.stringify(m, undefined, 2));
callback(m);
}
seq_carol = m.tx_json.sequence;
})
seq_carol = m.tx_json.sequence;
})
.submit();
},
function (callback) {
@@ -1982,13 +1986,12 @@ suite("Client Issue #535", function() {
},
callback);
},
], function (error) {
if (error)
//console.log("result: %s: error=%s", self.what, error);
assert(!error, self.what);
done();
});
], function (error) {
if (error)
//console.log("result: %s: error=%s", self.what, error);
assert(!error, self.what);
done();
});
});
});
// vim:sw=2:sts=2:ts=8:et

View File

@@ -1,115 +1,115 @@
var async = require('async');
var assert = require('assert');
var assert = require('assert-diff');
var Account = require('ripple-lib').UInt160;
var Remote = require('ripple-lib').Remote;
var Transaction = require('ripple-lib').Transaction;
var testutils = require('./testutils');
var config = testutils.init_config();
suite('Order Book', function() {
var $ = { };
setup(function(done) {
testutils.build_setup().call($, done);
});
teardown(function(done) {
testutils.build_teardown().call($, done);
});
test('Track offers', function (done) {
var self = this;
var steps = [
function(callback) {
self.what = 'Create accounts';
self.what = 'Create accounts';
testutils.create_accounts(
$.remote,
'root',
'20000.0',
[ 'mtgox', 'alice', 'bob' ],
callback
);
testutils.create_accounts(
$.remote,
'root',
'20000.0',
[ 'mtgox', 'alice', 'bob' ],
callback
);
},
function waitLedgers(callback) {
self.what = 'Wait ledger';
$.remote.once('ledger_closed', function() {
callback();
});
$.remote.ledger_accept();
},
function verifyBalance(callback) {
self.what = 'Verify balance';
testutils.verify_balance(
$.remote,
[ 'mtgox', 'alice', 'bob' ],
'20000000000',
'19999999988',
callback
);
},
function (callback) {
self.what = 'Set transfer rate';
var tx = $.remote.transaction('AccountSet', {
account: 'mtgox'
});
tx.transferRate(1.1 * 1e9);
tx.submit(function(err, m) {
assert.ifError(err);
assert.strictEqual(m.engine_result, 'tesSUCCESS');
callback();
});
testutils.ledger_wait($.remote, tx);
},
function (callback) {
self.what = 'Set limits';
testutils.credit_limits($.remote, {
'alice' : '1000/USD/mtgox',
'bob' : '1000/USD/mtgox'
},
callback);
},
function (callback) {
self.what = 'Distribute funds';
testutils.payments($.remote, {
'mtgox' : [ '100/USD/alice', '50/USD/bob' ]
},
callback);
},
function (callback) {
self.what = 'Create offer';
// get 4000/XRP pay 10/USD : offer pays 10 USD for 4000 XRP
var tx = $.remote.transaction('OfferCreate', {
account: 'alice',
taker_pays: '4000',
taker_gets: '10/USD/mtgox'
});
tx.submit(function(err, m) {
assert.ifError(err);
assert.strictEqual(m.engine_result, 'tesSUCCESS');
callback();
});
testutils.ledger_wait($.remote, tx);
},
function (callback) {
self.what = 'Create order book';
@@ -118,60 +118,60 @@ suite('Order Book', function() {
issuer_gets: Account.json_rewrite('mtgox'),
currency_gets: 'USD'
});
ob.on('model', function(){});
ob.getOffers(function(err, offers) {
assert.ifError(err);
//console.log('OFFERS', offers);
var expected = [
{ Account: 'rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn',
BookDirectory: 'AE0A97F385FFE42E3096BA3F98A0173090FE66A3C2482FE0570E35FA931A0000',
BookNode: '0000000000000000',
Flags: 0,
LedgerEntryType: 'Offer',
OwnerNode: '0000000000000000',
PreviousTxnID: offers[0].PreviousTxnID,
PreviousTxnLgrSeq: offers[0].PreviousTxnLgrSeq,
Sequence: 2,
TakerGets: { currency: 'USD',
issuer: 'rGihwhaqU8g7ahwAvTq6iX5rvsfcbgZw6v',
value: '10'
},
TakerPays: '4000',
index: 'CD6AE78EE0A5438978501A0404D9093597F57B705D566B5070D58BD48F98468C',
owner_funds: '100',
quality: '400',
is_fully_funded: true,
taker_gets_funded: '10',
taker_pays_funded: '4000' }
{ Account: 'rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn',
BookDirectory: 'AE0A97F385FFE42E3096BA3F98A0173090FE66A3C2482FE0570E35FA931A0000',
BookNode: '0000000000000000',
Flags: 0,
LedgerEntryType: 'Offer',
OwnerNode: '0000000000000000',
PreviousTxnID: offers[0].PreviousTxnID,
PreviousTxnLgrSeq: offers[0].PreviousTxnLgrSeq,
Sequence: 3,
TakerGets: { currency: 'USD',
issuer: 'rGihwhaqU8g7ahwAvTq6iX5rvsfcbgZw6v',
value: '10'
},
TakerPays: '4000',
index: '2A432F386EF28151AF60885CE201CC9331FF494A163D40531A9D253C97E81D61',
owner_funds: '100',
quality: '400',
is_fully_funded: true,
taker_gets_funded: '10',
taker_pays_funded: '4000' }
]
assert.deepEqual(offers, expected);
callback(null, ob);
});
},
function (ob, callback) {
self.what = 'Create offer';
// get 5/USD pay 2000/XRP: offer pays 2000 XRP for 5 USD
var tx = $.remote.transaction('OfferCreate', {
account: 'bob',
taker_pays: '5/USD/mtgox',
taker_gets: '2000',
});
tx.submit(function(err, m) {
assert.ifError(err);
assert.strictEqual(m.engine_result, 'tesSUCCESS');
callback(null, ob);
});
testutils.ledger_wait($.remote, tx);
tx.submit(function(err, m) {
assert.ifError(err);
assert.strictEqual(m.engine_result, 'tesSUCCESS');
callback(null, ob);
});
testutils.ledger_wait($.remote, tx);
},
function (ob, callback) {
self.what = 'Check order book tracking';
@@ -180,21 +180,21 @@ suite('Order Book', function() {
//console.log('OFFERS', offers);
var expected = [
{ Account: 'rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn',
BookDirectory: 'AE0A97F385FFE42E3096BA3F98A0173090FE66A3C2482FE0570E35FA931A0000',
BookNode: '0000000000000000',
Flags: 0,
LedgerEntryType: 'Offer',
OwnerNode: '0000000000000000',
PreviousTxnID: offers[0].PreviousTxnID,
PreviousTxnLgrSeq: offers[0].PreviousTxnLgrSeq,
Sequence: 2,
TakerGets:
{ currency: 'USD',
issuer: 'rGihwhaqU8g7ahwAvTq6iX5rvsfcbgZw6v',
value: '5' },
{ Account: 'rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn',
BookDirectory: 'AE0A97F385FFE42E3096BA3F98A0173090FE66A3C2482FE0570E35FA931A0000',
BookNode: '0000000000000000',
Flags: 0,
LedgerEntryType: 'Offer',
OwnerNode: '0000000000000000',
PreviousTxnID: offers[0].PreviousTxnID,
PreviousTxnLgrSeq: offers[0].PreviousTxnLgrSeq,
Sequence: 3,
TakerGets:
{ currency: 'USD',
issuer: 'rGihwhaqU8g7ahwAvTq6iX5rvsfcbgZw6v',
value: '5' },
TakerPays: '2000',
index: 'CD6AE78EE0A5438978501A0404D9093597F57B705D566B5070D58BD48F98468C',
index: '2A432F386EF28151AF60885CE201CC9331FF494A163D40531A9D253C97E81D61',
owner_funds: '94.5',
quality: '400',
is_fully_funded: true,
@@ -207,10 +207,10 @@ suite('Order Book', function() {
});
},
];
async.waterfall(steps, function (error) {
assert(!error, self.what + ': ' + error);
done();
});
async.waterfall(steps, function (error) {
assert(!error, self.what + ': ' + error);
done();
});
});
});

View File

@@ -133,7 +133,7 @@ make_suite('Robust transaction submission', function() {
function verifyBalance(callback) {
self.what = 'Verify balance';
testutils.verify_balance($.remote, 'bob', '20001000000', callback);
testutils.verify_balance($.remote, 'bob', '20000999988', callback);
}
]
@@ -218,7 +218,7 @@ make_suite('Robust transaction submission', function() {
function verifyBalance(callback) {
self.what = 'Verify balance';
testutils.verify_balance($.remote, 'alice', '20001000000', callback);
testutils.verify_balance($.remote, 'alice', '20000999988', callback);
}
]
@@ -249,7 +249,7 @@ make_suite('Robust transaction submission', function() {
function verifyBalance(callback) {
self.what = 'Verify balance';
testutils.verify_balance($.remote, 'alice', '20000000000', callback);
testutils.verify_balance($.remote, 'alice', '19999999988', callback);
},
function submitTransaction(callback) {
@@ -317,7 +317,7 @@ make_suite('Robust transaction submission', function() {
function verifyBalance(callback) {
self.what = 'Verify balance';
testutils.verify_balance($.remote, 'alice', '20001000000', callback);
testutils.verify_balance($.remote, 'alice', '20000999988', callback);
}
]
@@ -381,7 +381,7 @@ make_suite('Robust transaction submission', function() {
function verifyBalance(callback) {
self.what = 'Verify balance';
testutils.verify_balance($.remote, 'alice', '20001000000', callback);
testutils.verify_balance($.remote, 'alice', '20000999988', callback);
}
]

View File

@@ -215,6 +215,18 @@ function account_dump(remote, account, callback) {
// construct a json result
};
function set_account_flag(remote, account, options, callback) {
if (typeof options === 'number') {
options = { set_flag: options };
}
var tx = remote.createTransaction('AccountSet', extend({
account: account
}, options));
submit_transaction(tx, callback);
}
exports.fund_account =
fund_account =
function(remote, src, account, amount, callback) {
@@ -228,7 +240,7 @@ function(remote, src, account, amount, callback) {
tx.once('proposed', function (result) {
//console.log('proposed: %s', JSON.stringify(result));
callback(result.engine_result === 'tesSUCCESS' ? null : new Error());
callback(result.engine_result === 'tesSUCCESS' ? null : result);
});
tx.once('error', function (result) {
@@ -241,7 +253,14 @@ function(remote, src, account, amount, callback) {
exports.create_account =
create_account =
function(remote, src, account, amount, callback) {
function(remote, src, account, amount, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
options = extend({default_rippling: true}, options);
// Before creating the account, check if it exists in the ledger.
// If it does, regardless of the balance, fail the test, because
// the ledger is not in the expected state.
@@ -253,25 +272,39 @@ function(remote, src, account, amount, callback) {
});
info.once('error', function(result) {
if (result.error === "remoteError" && result.remote.error === "actNotFound") {
// rippled indicated the account does not exist. Create it by funding it.
fund_account(remote, src, account, amount, callback);
} else {
// Some other error occurred. Pass it up to the callback.
callback(result);
var isNotFoundError = result.error === 'remoteError'
&& result.remote.error === 'actNotFound';
if (!isNotFoundError) {
return callback(result);
}
// rippled indicated the account does not exist. Create it by funding it.
fund_account(remote, src, account, amount, function(err) {
if (err) {
callback(err);
} else if (!options.default_rippling) {
callback();
} else {
// Set default rippling on trustlines for account
set_account_flag(remote, account, 8, callback);
}
});
});
info.request();
}
function create_accounts(remote, src, amount, accounts, callback) {
assert.strictEqual(arguments.length, 5);
function create_accounts(remote, src, amount, accounts, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
remote.set_account_seq(src, 1);
async.forEach(accounts, function (account, callback) {
create_account(remote, src, account, amount, callback);
create_account(remote, src, account, amount, options, callback);
}, callback);
};
@@ -568,13 +601,18 @@ function ledger_wait(remote, tx) {
;(function nextLedger() {
remote.once('ledger_closed', function() {
if (!tx.finalized) {
setTimeout(nextLedger, isTravis ? 400 : 100);
setTimeout(nextLedger, isTravis ? 200 : 50);
}
});
remote.ledger_accept();
})();
};
function submit_transaction(tx, callback) {
tx.submit(callback);
ledger_wait(tx.remote, tx);
}
exports.account_dump = account_dump;
exports.build_setup = build_setup;
exports.build_teardown = build_teardown;
@@ -595,6 +633,7 @@ exports.verify_offer_not_found = verify_offer_not_found;
exports.verify_owner_count = verify_owner_count;
exports.verify_owner_counts = verify_owner_counts;
exports.ledger_wait = ledger_wait;
exports.submit_transaction = submit_transaction;
process.on('uncaughtException', function() {
Object.keys(server).forEach(function(host) {