Merge branch 'master' of github.com:jedmccaleb/NewCoin

This commit is contained in:
jed
2013-01-29 07:26:25 -08:00
13 changed files with 263 additions and 1003 deletions

View File

@@ -9,9 +9,14 @@
#include <boost/foreach.hpp>
#include <boost/bind.hpp>
#include "../ripple/JobQueue.h"
#include "../ripple/Log.h"
SETUP_NLOG("DataBase");
using namespace std;
SqliteDatabase::SqliteDatabase(const char* host) : Database(host,"",""), walRunning(false)
SqliteDatabase::SqliteDatabase(const char* host) : Database(host,"",""), mWalQ(NULL), walRunning(false)
{
mConnection = NULL;
mCurrentStmt = NULL;
@@ -22,7 +27,7 @@ void SqliteDatabase::connect()
int rc = sqlite3_open(mHost.c_str(), &mConnection);
if (rc)
{
cout << "Can't open database: " << mHost << " " << rc << endl;
cLog(lsFATAL) << "Can't open database: " << mHost << " " << rc;
sqlite3_close(mConnection);
assert((rc != SQLITE_BUSY) && (rc != SQLITE_LOCKED));
}
@@ -46,9 +51,9 @@ bool SqliteDatabase::executeSQL(const char* sql, bool fail_ok)
if (!fail_ok)
{
#ifdef DEBUG
cout << "SQL Perror:" << rc << endl;
cout << "Statement: " << sql << endl;
cout << "Error: " << sqlite3_errmsg(mConnection) << endl;
cLog(lsWARNING) << "SQL Perror:" << rc;
cLog(lsWARNING) << "Statement: " << sql;
cLog(lsWARNING) << "Error: " << sqlite3_errmsg(mConnection);
#endif
}
return false;
@@ -70,9 +75,9 @@ bool SqliteDatabase::executeSQL(const char* sql, bool fail_ok)
if (!fail_ok)
{
#ifdef DEBUG
cout << "SQL Serror:" << rc << endl;
cout << "Statement: " << sql << endl;
cout << "Error: " << sqlite3_errmsg(mConnection) << endl;
cLog(lsWARNING) << "SQL Serror:" << rc;
cLog(lsWARNING) << "Statement: " << sql;
cLog(lsWARNING) << "Error: " << sqlite3_errmsg(mConnection);
#endif
}
return false;
@@ -130,7 +135,7 @@ bool SqliteDatabase::getNextRow()
else
{
assert((rc != SQLITE_BUSY) && (rc != SQLITE_LOCKED));
cout << "SQL Rerror:" << rc << endl;
cLog(lsWARNING) << "SQL Rerror:" << rc;
return(false);
}
}
@@ -194,27 +199,32 @@ static int SqliteWALHook(void *s, sqlite3* dbCon, const char *dbName, int walSiz
return SQLITE_OK;
}
bool SqliteDatabase::setupCheckpointing()
bool SqliteDatabase::setupCheckpointing(JobQueue *q)
{
mWalQ = q;
sqlite3_wal_hook(mConnection, SqliteWALHook, this);
return true;
}
void SqliteDatabase::doHook(const char *db, int pages)
{
if (pages < 256)
if (pages < 512)
return;
boost::mutex::scoped_lock sl(walMutex);
if (walDBs.insert(db).second && !walRunning)
{
walRunning = true;
boost::thread(boost::bind(&SqliteDatabase::runWal, this)).detach();
if (mWalQ)
mWalQ->addJob(jtWAL, boost::bind(&SqliteDatabase::runWal, this));
else
boost::thread(boost::bind(&SqliteDatabase::runWal, this)).detach();
}
}
void SqliteDatabase::runWal()
{
std::set<std::string> walSet;
std::string name = sqlite3_db_filename(mConnection, "main");
while (1)
{
@@ -231,10 +241,14 @@ void SqliteDatabase::runWal()
BOOST_FOREACH(const std::string& db, walSet)
{
int log, ckpt;
sqlite3_wal_checkpoint_v2(mConnection, db.c_str(), SQLITE_CHECKPOINT_PASSIVE, &log, &ckpt);
int ret = sqlite3_wal_checkpoint_v2(mConnection, db.c_str(), SQLITE_CHECKPOINT_PASSIVE, &log, &ckpt);
if (ret != SQLITE_OK)
{
cLog((ret == SQLITE_LOCKED) ? lsDEBUG : lsWARNING) << "WAL "
<< sqlite3_db_filename(mConnection, "main") << " / " << db << " errror " << ret;
}
}
walSet.clear();
}
}

View File

@@ -16,6 +16,7 @@ class SqliteDatabase : public Database
bool mMoreRows;
boost::mutex walMutex;
JobQueue* mWalQ;
std::set<std::string> walDBs;
bool walRunning;
@@ -51,7 +52,7 @@ public:
uint64 getBigInt(int colIndex);
sqlite3* peekConnection() { return mConnection; }
virtual bool setupCheckpointing();
virtual bool setupCheckpointing(JobQueue*);
virtual SqliteDatabase* getSqliteDB() { return this; }
void runWal();

View File

@@ -18,6 +18,7 @@
*/
class SqliteDatabase;
class JobQueue;
class Database
{
@@ -86,8 +87,8 @@ public:
// float getSingleDBValueFloat(const char* sql);
// char* getSingleDBValueStr(const char* sql, std::string& retStr);
virtual bool setupCheckpointing() { return false; }
virtual SqliteDatabase* getSqliteDB() { return NULL; }
virtual bool setupCheckpointing(JobQueue*) { return false; }
virtual SqliteDatabase* getSqliteDB() { return NULL; }
};
#endif

View File

@@ -1023,7 +1023,7 @@ STAmount STAmount::setRate(uint64 rate)
// --> uOfferPaysRate: >= QUALITY_ONE | TransferRate for third party IOUs paid by offer owner.
// --> saOfferRate: Original saOfferGets/saOfferPays, when offer was made.
// --> saOfferFunds: Limit for saOfferPays : How much can pay including fees.
// --> saTakerFunds: Limit for saOfferGets : How much taker really wants. : Driver
// --> saTakerFunds: Limit for saOfferGets : How much can pay including fees.
// --> saOfferPays: Request : this should be reduced as the offer is fullfilled.
// --> saOfferGets: Request : this should be reduced as the offer is fullfilled.
// --> saTakerPays: Limit for taker to Pays.
@@ -1050,7 +1050,7 @@ bool STAmount::applyOffer(
// Limit offerer funds available, by transfer fees.
STAmount saOfferFundsAvailable = QUALITY_ONE == uOfferPaysRate
? saOfferFunds
: STAmount::divide(saOfferFunds, STAmount(CURRENCY_ONE, uOfferPaysRate, -9));
: STAmount::divide(saOfferFunds, STAmount(CURRENCY_ONE, ACCOUNT_ONE, uOfferPaysRate, -9));
cLog(lsINFO) << "applyOffer: uOfferPaysRate=" << uOfferPaysRate;
cLog(lsINFO) << "applyOffer: saOfferFundsAvailable=" << saOfferFundsAvailable.getFullText();
@@ -1058,8 +1058,9 @@ bool STAmount::applyOffer(
// Limit taker funds available, by transfer fees.
STAmount saTakerFundsAvailable = QUALITY_ONE == uTakerPaysRate
? saTakerFunds
: STAmount::divide(saTakerFunds, STAmount(CURRENCY_ONE, uTakerPaysRate, -9));
: STAmount::divide(saTakerFunds, STAmount(CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9));
cLog(lsINFO) << "applyOffer: TAKER_FEES=" << STAmount(CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9).getFullText();
cLog(lsINFO) << "applyOffer: uTakerPaysRate=" << uTakerPaysRate;
cLog(lsINFO) << "applyOffer: saTakerFundsAvailable=" << saTakerFundsAvailable.getFullText();
@@ -1085,10 +1086,14 @@ bool STAmount::applyOffer(
cLog(lsINFO) << "applyOffer: saOfferGetsAvailable=" << saOfferGetsAvailable.getFullText();
STAmount saTakerPaysAvailable = std::min(saTakerPays, saTakerFundsAvailable);
cLog(lsINFO) << "applyOffer: saTakerPaysAvailable=" << saTakerPaysAvailable.getFullText();
STAmount saTakerPaysMax = std::min(saTakerPaysAvailable, saOfferGetsAvailable);
cLog(lsINFO) << "applyOffer: saTakerPaysMax=" << saTakerPaysMax.getFullText();
STAmount saTakerGetsMax = saTakerPaysMax >= saOfferGetsAvailable
? saOfferPaysAvailable // Potentially take entire offer. Avoid math shenanigans.
: std::min(saOfferPaysAvailable, multiply(saTakerPaysMax, saOfferRate, saTakerGets)); // Taker a portion of offer.
: std::min(saOfferPaysAvailable, divide(saTakerPaysMax, saOfferRate, saTakerGets)); // Taker a portion of offer.
cLog(lsINFO) << "applyOffer: saOfferRate=" << saOfferRate.getFullText();
cLog(lsINFO) << "applyOffer: saTakerGetsMax=" << saTakerGetsMax.getFullText();
saTakerGot = std::min(saTakerGets, saTakerGetsMax); // Limit by wanted.
saTakerPaid = saTakerGot == saOfferPaysAvailable
@@ -1106,11 +1111,12 @@ bool STAmount::applyOffer(
{
// Compute fees in a rounding safe way.
// TakerCost includes transfer fees.
STAmount saTakerCost = STAmount::multiply(saTakerPaid, STAmount(CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9));
STAmount saTransferRate = STAmount(CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9);
cLog(lsINFO) << "applyOffer: saTransferRate=" << saTransferRate.getFullText();
// TakerCost includes transfer fees.
STAmount saTakerCost = STAmount::multiply(saTakerPaid, saTransferRate);
cLog(lsINFO) << "applyOffer: saTakerCost=" << saTakerCost.getFullText();
cLog(lsINFO) << "applyOffer: saTakerFunds=" << saTakerFunds.getFullText();
saTakerIssuerFee = saTakerCost > saTakerFunds

View File

@@ -62,10 +62,10 @@ void Application::stop()
{
cLog(lsINFO) << "Received shutdown request";
mIOService.stop();
mJobQueue.shutdown();
mHashedObjectStore.bulkWrite();
mValidations.flush();
mAuxService.stop();
mJobQueue.shutdown();
cLog(lsINFO) << "Stopped: " << mIOService.stopped();
Instance::shutdown();
@@ -122,9 +122,9 @@ void Application::setup()
boost::thread t5(boost::bind(&InitDB, &mHashNodeDB, "hashnode.db", HashNodeDBInit, HashNodeDBCount));
boost::thread t6(boost::bind(&InitDB, &mNetNodeDB, "netnode.db", NetNodeDBInit, NetNodeDBCount));
t1.join(); t2.join(); t3.join(); t4.join(); t5.join(); t6.join();
mTxnDB->getDB()->setupCheckpointing();
mLedgerDB->getDB()->setupCheckpointing();
mHashNodeDB->getDB()->setupCheckpointing();
mTxnDB->getDB()->setupCheckpointing(&mJobQueue);
mLedgerDB->getDB()->setupCheckpointing(&mJobQueue);
mHashNodeDB->getDB()->setupCheckpointing(&mJobQueue);
if (theConfig.START_UP == Config::FRESH)
{

View File

@@ -16,6 +16,7 @@ JobQueue::JobQueue() : mLastJob(0), mThreadCount(0), mShuttingDown(false)
mJobLoads[jtTRANSACTION].setTargetLatency(250, 1000);
mJobLoads[jtPROPOSAL_ut].setTargetLatency(500, 1250);
mJobLoads[jtPUBLEDGER].setTargetLatency(1000, 2500);
mJobLoads[jtWAL].setTargetLatency(1000, 2500);
mJobLoads[jtVALIDATION_t].setTargetLatency(500, 1500);
mJobLoads[jtWRITE].setTargetLatency(750, 1500);
mJobLoads[jtTRANSACTION_l].setTargetLatency(100, 500);
@@ -41,6 +42,7 @@ const char* Job::toString(JobType t)
case jtTRANSACTION: return "transaction";
case jtPUBLEDGER: return "publishLedger";
case jtVALIDATION_t: return "trustedValidation";
case jtWAL: return "writeAhead";
case jtWRITE: return "writeObjects";
case jtTRANSACTION_l: return "localTransaction";
case jtPROPOSAL_t: return "trustedProposal";

View File

@@ -27,12 +27,13 @@ enum JobType
jtCLIENT = 4, // A websocket command from the client
jtTRANSACTION = 5, // A transaction received from the network
jtPUBLEDGER = 6, // Publish a fully-accepted ledger
jtVALIDATION_t = 7, // A validation from a trusted source
jtWRITE = 8, // Write out hashed objects
jtTRANSACTION_l = 9, // A local transaction
jtPROPOSAL_t = 10, // A proposal from a trusted source
jtADMIN = 11, // An administrative operation
jtDEATH = 12, // job of death, used internally
jtWAL = 7, // Write-ahead logging
jtVALIDATION_t = 8, // A validation from a trusted source
jtWRITE = 9, // Write out hashed objects
jtTRANSACTION_l = 10, // A local transaction
jtPROPOSAL_t = 11, // A proposal from a trusted source
jtADMIN = 12, // An administrative operation
jtDEATH = 13, // job of death, used internally
// special types not dispatched by the job pool
jtPEER = 17,

View File

@@ -1129,21 +1129,25 @@ STAmount LedgerEntrySet::accountFunds(const uint160& uAccountID, const STAmount&
// Calculate transit fee.
STAmount LedgerEntrySet::rippleTransferFee(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount)
{
STAmount saTransitFee;
if (uSenderID != uIssuerID && uReceiverID != uIssuerID)
{
uint32 uTransitRate = rippleTransferRate(uIssuerID);
if (QUALITY_ONE != uTransitRate)
{
STAmount saTransitRate(CURRENCY_ONE, uTransitRate, -9);
STAmount saTransitRate(CURRENCY_ONE, ACCOUNT_ONE, uTransitRate, -9);
saTransitFee = STAmount::multiply(saAmount, saTransitRate, saAmount.getCurrency(), saAmount.getIssuer());
STAmount saTransferTotal = STAmount::multiply(saAmount, saTransitRate, saAmount.getCurrency(), saAmount.getIssuer());
STAmount saTransferFee = saTransferTotal-saAmount;
cLog(lsINFO) << boost::str(boost::format("rippleTransferFee: saTransferFee=%s")
% saTransferFee.getFullText());
return saTransferFee;
}
}
return saTransitFee;
return STAmount(saAmount.getCurrency(), saAmount.getIssuer());
}
TER LedgerEntrySet::trustCreate(
@@ -1239,7 +1243,7 @@ TER LedgerEntrySet::rippleCredit(const uint160& uSenderID, const uint160& uRecei
saBalance.setIssuer(ACCOUNT_ONE);
cLog(lsDEBUG) << boost::str(boost::format("rippleCredit: create line: %s (0) -> %s : %s")
cLog(lsDEBUG) << boost::str(boost::format("rippleCredit: create line: %s --> %s : %s")
% RippleAddress::createHumanAccountID(uSenderID)
% RippleAddress::createHumanAccountID(uReceiverID)
% saAmount.getFullText());
@@ -1258,21 +1262,21 @@ TER LedgerEntrySet::rippleCredit(const uint160& uSenderID, const uint160& uRecei
{
STAmount saBalance = sleRippleState->getFieldAmount(sfBalance);
if (!bSenderHigh)
saBalance.negate(); // Put balance in low terms.
if (bSenderHigh)
saBalance.negate(); // Put balance in sender terms.
STAmount saBefore = saBalance;
saBalance += saAmount;
saBalance -= saAmount;
cLog(lsDEBUG) << boost::str(boost::format("rippleCredit: %s -- (%s > %s) -> %s : %s")
cLog(lsDEBUG) << boost::str(boost::format("rippleCredit: %s --> %s : before=%s amount=%s after=%s")
% RippleAddress::createHumanAccountID(uSenderID)
% saBefore.getFullText()
% saBalance.getFullText()
% RippleAddress::createHumanAccountID(uReceiverID)
% saAmount.getFullText());
% saBefore.getFullText()
% saAmount.getFullText()
% saBalance.getFullText());
if (!bSenderHigh)
if (bSenderHigh)
saBalance.negate();
sleRippleState->setFieldAmount(sfBalance, saBalance);
@@ -1286,8 +1290,8 @@ TER LedgerEntrySet::rippleCredit(const uint160& uSenderID, const uint160& uRecei
}
// Send regardless of limits.
// --> saAmount: Amount/currency/issuer for receiver to get.
// <-- saActual: Amount actually sent. Sender pay's fees.
// --> saAmount: Amount/currency/issuer to deliver to reciever.
// <-- saActual: Amount actually cost. Sender pay's fees.
TER LedgerEntrySet::rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, STAmount& saActual)
{
const uint160 uIssuerID = saAmount.getIssuer();
@@ -1314,12 +1318,12 @@ TER LedgerEntrySet::rippleSend(const uint160& uSenderID, const uint160& uReceive
saActual.setIssuer(uIssuerID); // XXX Make sure this done in + above.
cLog(lsINFO) << boost::str(boost::format("rippleSend> %s -- %s--> %s (%s) : %s")
cLog(lsINFO) << boost::str(boost::format("rippleSend> %s -- > %s : deliver=%s fee=%s cost=%s")
% RippleAddress::createHumanAccountID(uSenderID)
% saTransitFee.getFullText()
% RippleAddress::createHumanAccountID(uReceiverID)
% saActual.getFullText()
% saAmount.getFullText());
% saAmount.getFullText()
% saTransitFee.getFullText()
% saActual.getFullText());
terResult = rippleCredit(uIssuerID, uReceiverID, saAmount);

View File

@@ -15,6 +15,7 @@
// Put at the beginning of a C++ file that needs its own log partition
#define SETUP_LOG() static LogPartition logPartition(__FILE__)
#define SETUP_NLOG(x) static LogPartition logPartition(x)
// Standard conditional log
#define cLog(x) if (!logPartition.doLog(x)) do {} while (0); else Log(x, logPartition)

View File

@@ -26,6 +26,13 @@ TER OfferCreateTransactor::takeOffers(
STAmount& saTakerGot,
bool& bUnfunded)
{
// The book has the most elements. Take the perspective of the book.
// Book is ordered for taker: taker pays / taker gets, < is better
// The order is for the other books currencys for get and pays are opposites.
// We want the same ratio for the respective currencies.
// So we swap paid and gets for determing take quality.
assert(saTakerPays && saTakerGets);
cLog(lsINFO) << "takeOffers: against book: " << uBookBase.ToString();
@@ -58,10 +65,13 @@ TER OfferCreateTransactor::takeOffers(
sleOfferDir = mEngine->entryCache(ltDIR_NODE, mEngine->getLedger()->getNextLedgerIndex(uTipIndex, uBookEnd));
if (sleOfferDir)
{
cLog(lsINFO) << "takeOffers: possible counter offer found";
uTipIndex = sleOfferDir->getIndex();
uTipQuality = Ledger::getQuality(uTipIndex);
cLog(lsINFO) << boost::str(boost::format("takeOffers: possible counter offer found: uTipQuality=%d uTipQuality=%s")
% uTipQuality
% uTipIndex.ToString());
}
else
{
@@ -77,7 +87,25 @@ TER OfferCreateTransactor::takeOffers(
|| (bPassive && uTakeQuality == uTipQuality))
{
// Done.
cLog(lsINFO) << "takeOffers: done";
STAmount saTipRate = sleOfferDir ? STAmount::setRate(uTipQuality) : saTakerRate;
cLog(lsINFO) << boost::str(boost::format("takeOffers: done: dir=%d uTakeQuality=%d %c uTipQuality=%d saTakerRate=%s %c saTipRate=%s bPassive=%d")
% !!sleOfferDir
% uTakeQuality
% (uTakeQuality == uTipQuality
? '='
: uTakeQuality < uTipQuality
? '<'
: '>')
% uTipQuality
% saTakerRate
% (saTakerRate == saTipRate
? '='
: saTakerRate < saTipRate
? '<'
: '>')
% saTipRate
% bPassive);
terResult = tesSUCCESS;
}
@@ -143,9 +171,6 @@ TER OfferCreateTransactor::takeOffers(
}
else
{
STAmount saPay = saTakerPays - saTakerPaid;
if (saTakerFunds < saPay)
saPay = saTakerFunds;
STAmount saSubTakerPaid;
STAmount saSubTakerGot;
STAmount saTakerIssuerFee;
@@ -156,7 +181,6 @@ TER OfferCreateTransactor::takeOffers(
cLog(lsINFO) << "takeOffers: applyOffer: saTakerPaid: " << saTakerPaid.getFullText();
cLog(lsINFO) << "takeOffers: applyOffer: saTakerFunds: " << saTakerFunds.getFullText();
cLog(lsINFO) << "takeOffers: applyOffer: saOfferFunds: " << saOfferFunds.getFullText();
cLog(lsINFO) << "takeOffers: applyOffer: saPay: " << saPay.getFullText();
cLog(lsINFO) << "takeOffers: applyOffer: saOfferPays: " << saOfferPays.getFullText();
cLog(lsINFO) << "takeOffers: applyOffer: saOfferGets: " << saOfferGets.getFullText();
cLog(lsINFO) << "takeOffers: applyOffer: saOfferRate: " << saOfferRate.getFullText();
@@ -168,7 +192,7 @@ TER OfferCreateTransactor::takeOffers(
mEngine->getNodes().rippleTransferRate(uOfferOwnerID, uTakerAccountID, uTakerGetsAccountID),
saOfferRate,
saOfferFunds,
saPay,
saTakerFunds,
saOfferPays,
saOfferGets,
saTakerPays,
@@ -221,17 +245,28 @@ TER OfferCreateTransactor::takeOffers(
{
terResult = mEngine->getNodes().accountSend(uOfferOwnerID, uTakerAccountID, saSubTakerGot); // Offer owner pays taker.
if (tesSUCCESS == terResult)
terResult = mEngine->getNodes().accountSend(uOfferOwnerID, uTakerGetsAccountID, saOfferIssuerFee); // Offer owner pays issuer transfer fee.
// if (tesSUCCESS == terResult)
// terResult = mEngine->getNodes().accountSend(uOfferOwnerID, uTakerGetsAccountID, saOfferIssuerFee); // Offer owner pays issuer transfer fee.
if (tesSUCCESS == terResult)
terResult = mEngine->getNodes().accountSend(uTakerAccountID, uOfferOwnerID, saSubTakerPaid); // Taker pays offer owner.
if (tesSUCCESS == terResult)
terResult = mEngine->getNodes().accountSend(uTakerAccountID, uTakerPaysAccountID, saTakerIssuerFee); // Taker pays issuer transfer fee.
// if (tesSUCCESS == terResult)
// terResult = mEngine->getNodes().accountSend(uTakerAccountID, uTakerPaysAccountID, saTakerIssuerFee); // Taker pays issuer transfer fee.
// Reduce amount considered paid by taker's rate (not actual cost).
saTakerPaid += std::min(saPay, STAmount::multiply(saSubTakerGot, saTakerRate, saPay));
STAmount saPay = saTakerPays - saTakerPaid;
if (saTakerFunds < saPay)
saPay = saTakerFunds;
STAmount saTakerUsed = STAmount::multiply(saSubTakerGot, saTakerRate, saTakerPays);
cLog(lsINFO) << "takeOffers: applyOffer: saPay: " << saPay.getFullText();
cLog(lsINFO) << "takeOffers: applyOffer: saSubTakerGot: " << saSubTakerGot.getFullText();
cLog(lsINFO) << "takeOffers: applyOffer: saTakerRate: " << saTakerRate.getFullText();
cLog(lsINFO) << "takeOffers: applyOffer: saTakerUsed: " << saTakerUsed.getFullText();
saTakerPaid += std::min(saPay, saTakerUsed);
saTakerGot += saSubTakerGot;
if (tesSUCCESS == terResult)
@@ -368,8 +403,8 @@ TER OfferCreateTransactor::doApply()
}
}
STAmount saOfferPaid;
STAmount saOfferGot;
STAmount saPaid;
STAmount saGot;
bool bUnfunded = false;
if (tesSUCCESS == terResult)
@@ -383,27 +418,29 @@ TER OfferCreateTransactor::doApply()
// Take using the parameters of the offer.
cLog(lsWARNING) << "OfferCreate: takeOffers: BEFORE saTakerGets=" << saTakerGets.getFullText();
terResult = takeOffers(
bPassive,
uTakeBookBase,
mTxnAccountID,
mTxnAccount,
saTakerGets,
saTakerGets, // Reverse as we are the taker for taking.
saTakerPays,
saOfferPaid, // How much would have spent at full price.
saOfferGot, // How much was got.
saPaid, // How much would have spent at full price.
saGot, // How much was got.
bUnfunded);
cLog(lsWARNING) << "OfferCreate: takeOffers=" << terResult;
cLog(lsWARNING) << "OfferCreate: takeOffers: saOfferPaid=" << saOfferPaid.getFullText();
cLog(lsWARNING) << "OfferCreate: takeOffers: saOfferGot=" << saOfferGot.getFullText();
cLog(lsWARNING) << "OfferCreate: takeOffers: saTakerPays=" << saTakerPays.getFullText();
cLog(lsWARNING) << "OfferCreate: takeOffers: AFTER saTakerGets=" << saTakerGets.getFullText();
cLog(lsWARNING) << "OfferCreate: takeOffers: saPaid=" << saPaid.getFullText();
cLog(lsWARNING) << "OfferCreate: takeOffers: saGot=" << saGot.getFullText();
if (tesSUCCESS == terResult && !bUnfunded)
{
saTakerPays -= saOfferGot; // Reduce payin from takers by what offer just got.
saTakerGets -= saOfferPaid; // Reduce payout to takers by what srcAccount just paid.
saTakerPays -= saGot; // Reduce pay in from takers by what offer just got.
saTakerGets -= saPaid; // Reduce pay out to takers by what srcAccount just paid.
cLog(lsWARNING) << "OfferCreate: takeOffers: AFTER saTakerPays=" << saTakerPays.getFullText();
cLog(lsWARNING) << "OfferCreate: takeOffers: AFTER saTakerGets=" << saTakerGets.getFullText();
}
}
@@ -431,7 +468,7 @@ TER OfferCreateTransactor::doApply()
// Hope for more reserve to come in or more offers to consume.
terResult = tecINSUF_RESERVE_OFFER;
}
else if (!saOfferPaid && !saOfferGot)
else if (!saPaid && !saGot)
{
// Ledger is final, insufficent reserve to create offer, processed nothing.

View File

@@ -1009,7 +1009,7 @@ Amount.prototype.parse_native = function (j) {
var m;
if ('string' === typeof j)
m = j.match(/^(-?)(\d+)(\.\d{0,6})?$/);
m = j.match(/^(-?)(\d*)(\.\d{0,6})?$/);
if (m) {
if (undefined === m[3]) {
@@ -1056,8 +1056,8 @@ Amount.prototype.parse_value = function (j) {
}
else if ('string' === typeof j) {
var i = j.match(/^(-?)(\d+)$/);
var d = !i && j.match(/^(-?)(\d+)\.(\d*)$/);
var e = !e && j.match(/^(-?)(\d+)e(-?\d+)$/);
var d = !i && j.match(/^(-?)(\d*)\.(\d*)$/);
var e = !e && j.match(/^(-?)(\d*)e(-?\d+)$/);
if (e) {
// e notation

View File

@@ -1349,4 +1349,118 @@ buster.testCase("Offer cross currency", {
},
});
buster.testCase("Offer tests 3", {
'setUp' : testutils.build_setup(),
// 'setUp' : testutils.build_setup({ verbose: true }),
// 'setUp' : testutils.build_setup({ verbose: true, standalone: true }),
'tearDown' : testutils.build_teardown(),
"offer create then cross offer" :
function (done) {
var self = this;
var final_create;
async.waterfall([
function (callback) {
self.what = "Create accounts.";
testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "mtgox"], callback);
},
function (callback) {
self.what = "Set transfer rate.";
self.remote.transaction()
.account_set("mtgox")
.transfer_rate(1005000000)
.once('proposed', function (m) {
// console.log("proposed: %s", JSON.stringify(m));
callback(m.result !== 'tesSUCCESS');
})
.submit();
},
function (callback) {
self.what = "Set limits.";
testutils.credit_limits(self.remote,
{
"alice" : "1000/USD/mtgox",
"bob" : "1000/USD/mtgox",
"mtgox" : "50/USD/alice",
},
callback);
},
function (callback) {
self.what = "Distribute funds.";
testutils.payments(self.remote,
{
"mtgox" : [ "1/USD/bob" ],
"alice" : [ "50/USD/mtgox" ]
},
callback);
},
function (callback) {
self.what = "Set limits 2.";
testutils.credit_limits(self.remote,
{
"mtgox" : "0/USD/alice",
},
callback);
},
function (callback) {
self.what = "Create offer alice.";
self.remote.transaction()
.offer_create("alice", "50/USD/mtgox", "150000.0")
.on('proposed', function (m) {
// console.log("proposed: offer_create: %s", json.stringify(m));
callback(m.result !== 'tesSUCCESS');
seq_carol = m.tx_json.sequence;
})
.submit();
},
function (callback) {
self.what = "Create offer bob.";
self.remote.transaction()
.offer_create("bob", "100.0", ".1/USD/mtgox")
.on('proposed', function (m) {
// console.log("proposed: offer_create: %s", json.stringify(m));
callback(m.result !== 'tesSUCCESS');
seq_carol = m.tx_json.sequence;
})
.submit();
},
// function (callback) {
// self.what = "Display ledger";
//
// self.remote.request_ledger('current', true)
// .on('success', function (m) {
// console.log("Ledger: %s", JSON.stringify(m, undefined, 2));
//
// callback();
// })
// .request();
// },
function (callback) {
self.what = "Verify balances.";
testutils.verify_balances(self.remote,
{
"alice" : "-49.96666666666667/USD/mtgox",
"bob" : "0.9665/USD/mtgox",
},
callback);
},
], function (error) {
// console.log("result: error=%s", error);
buster.refute(error);
done();
});
},
});
// vim:sw=2:sts=2:ts=8:et

View File

@@ -1,921 +0,0 @@
var async = require("async");
var buster = require("buster");
var Amount = require("../src/js/amount").Amount;
var Remote = require("../src/js/remote").Remote;
var Transaction = require("../src/js/transaction").Transaction;
var Server = require("./server").Server;
var testutils = require("./testutils");
require("../src/js/amount").config = require("./config");
require("../src/js/remote").config = require("./config");
buster.testRunner.timeout = 5000;
buster.testCase("Reserve", {
'setUp' : testutils.build_setup(),
// 'setUp' : testutils.build_setup({ verbose: true, standalone: false }),
'tearDown' : testutils.build_teardown(),
"offer create then cancel in one ledger" :
function (done) {
var self = this;
var final_create;
async.waterfall([
function (callback) {
self.remote.transaction()
.offer_create("root", "500", "100/USD/root")
.on('proposed', function (m) {
// console.log("PROPOSED: offer_create: %s", JSON.stringify(m));
callback(m.result !== 'tesSUCCESS', m);
})
.on('final', function (m) {
// console.log("FINAL: offer_create: %s", JSON.stringify(m));
buster.assert.equals('tesSUCCESS', m.metadata.TransactionResult);
buster.assert(final_create);
})
.submit();
},
function (m, callback) {
self.remote.transaction()
.offer_cancel("root", m.tx_json.Sequence)
.on('proposed', function (m) {
// console.log("PROPOSED: offer_cancel: %s", JSON.stringify(m));
callback(m.result !== 'tesSUCCESS', m);
})
.on('final', function (m) {
// console.log("FINAL: offer_cancel: %s", JSON.stringify(m, undefined, 2));
buster.assert.equals('tesSUCCESS', m.metadata.TransactionResult);
buster.assert(final_create);
done();
})
.submit();
},
function (m, callback) {
self.remote
.once('ledger_closed', function (message) {
// console.log("LEDGER_CLOSED: %d: %s", ledger_index, ledger_hash);
final_create = message;
})
.ledger_accept();
}
], function (error) {
// console.log("result: error=%s", error);
buster.refute(error);
if (error) done();
});
},
"offer_create then ledger_accept then offer_cancel then ledger_accept." :
function (done) {
var self = this;
var final_create;
var offer_seq;
async.waterfall([
function (callback) {
self.remote.transaction()
.offer_create("root", "500", "100/USD/root")
.on('proposed', function (m) {
// console.log("PROPOSED: offer_create: %s", JSON.stringify(m));
offer_seq = m.tx_json.Sequence;
callback(m.result !== 'tesSUCCESS');
})
.on('final', function (m) {
// console.log("FINAL: offer_create: %s", JSON.stringify(m));
buster.assert.equals('tesSUCCESS', m.metadata.TransactionResult);
final_create = m;
callback();
})
.submit();
},
function (callback) {
if (!final_create) {
self.remote
.once('ledger_closed', function (mesage) {
// console.log("LEDGER_CLOSED: %d: %s", ledger_index, ledger_hash);
})
.ledger_accept();
}
else {
callback();
}
},
function (callback) {
// console.log("CANCEL: offer_cancel: %d", offer_seq);
self.remote.transaction()
.offer_cancel("root", offer_seq)
.on('proposed', function (m) {
// console.log("PROPOSED: offer_cancel: %s", JSON.stringify(m));
callback(m.result !== 'tesSUCCESS');
})
.on('final', function (m) {
// console.log("FINAL: offer_cancel: %s", JSON.stringify(m));
buster.assert.equals('tesSUCCESS', m.metadata.TransactionResult);
buster.assert(final_create);
done();
})
.submit();
},
// See if ledger_accept will crash.
function (callback) {
self.remote
.once('ledger_closed', function (mesage) {
// console.log("LEDGER_CLOSED: A: %d: %s", ledger_index, ledger_hash);
callback();
})
.ledger_accept();
},
function (callback) {
self.remote
.once('ledger_closed', function (mesage) {
// console.log("LEDGER_CLOSED: B: %d: %s", ledger_index, ledger_hash);
callback();
})
.ledger_accept();
},
], function (error) {
// console.log("result: error=%s", error);
buster.refute(error);
if (error) done();
});
},
"//new user offer_create then ledger_accept then offer_cancel then ledger_accept." :
function (done) {
var self = this;
var final_create;
var offer_seq;
async.waterfall([
function (callback) {
self.remote.transaction()
.payment('root', 'alice', "1000")
.on('proposed', function (m) {
// console.log("proposed: %s", JSON.stringify(m));
buster.assert.equals(m.result, 'tesSUCCESS');
callback();
})
.submit()
},
function (callback) {
self.remote.transaction()
.offer_create("alice", "500", "100/USD/alice")
.on('proposed', function (m) {
// console.log("PROPOSED: offer_create: %s", JSON.stringify(m));
offer_seq = m.tx_json.Sequence;
callback(m.result !== 'tesSUCCESS');
})
.on('final', function (m) {
// console.log("FINAL: offer_create: %s", JSON.stringify(m));
buster.assert.equals('tesSUCCESS', m.metadata.TransactionResult);
final_create = m;
callback();
})
.submit();
},
function (callback) {
if (!final_create) {
self.remote
.once('ledger_closed', function (mesage) {
// console.log("LEDGER_CLOSED: %d: %s", ledger_index, ledger_hash);
})
.ledger_accept();
}
else {
callback();
}
},
function (callback) {
// console.log("CANCEL: offer_cancel: %d", offer_seq);
self.remote.transaction()
.offer_cancel("alice", offer_seq)
.on('proposed', function (m) {
// console.log("PROPOSED: offer_cancel: %s", JSON.stringify(m));
callback(m.result !== 'tesSUCCESS');
})
.on('final', function (m) {
// console.log("FINAL: offer_cancel: %s", JSON.stringify(m));
buster.assert.equals('tesSUCCESS', m.metadata.TransactionResult);
buster.assert(final_create);
done();
})
.submit();
},
// See if ledger_accept will crash.
function (callback) {
self.remote
.once('ledger_closed', function (mesage) {
// console.log("LEDGER_CLOSED: A: %d: %s", ledger_index, ledger_hash);
callback();
})
.ledger_accept();
},
function (callback) {
self.remote
.once('ledger_closed', function (mesage) {
// console.log("LEDGER_CLOSED: B: %d: %s", ledger_index, ledger_hash);
callback();
})
.ledger_accept();
},
], function (error) {
// console.log("result: error=%s", error);
buster.refute(error);
if (error) done();
});
},
"offer cancel past and future sequence" :
function (done) {
var self = this;
var final_create;
async.waterfall([
function (callback) {
self.remote.transaction()
.payment('root', 'alice', Amount.from_json("10000.0"))
.on('proposed', function (m) {
// console.log("PROPOSED: CreateAccount: %s", JSON.stringify(m));
callback(m.result !== 'tesSUCCESS', m);
})
.on('error', function(m) {
// console.log("error: %s", m);
buster.assert(false);
callback(m);
})
.submit();
},
// Past sequence but wrong
function (m, callback) {
self.remote.transaction()
.offer_cancel("root", m.tx_json.Sequence)
.on('proposed', function (m) {
// console.log("PROPOSED: offer_cancel past: %s", JSON.stringify(m));
callback(m.result !== 'tesSUCCESS', m);
})
.submit();
},
// Same sequence
function (m, callback) {
self.remote.transaction()
.offer_cancel("root", m.tx_json.Sequence+1)
.on('proposed', function (m) {
// console.log("PROPOSED: offer_cancel same: %s", JSON.stringify(m));
callback(m.result !== 'temBAD_SEQUENCE', m);
})
.submit();
},
// Future sequence
function (m, callback) {
// After a malformed transaction, need to recover correct sequence.
self.remote.set_account_seq("root", self.remote.account_seq("root")-1);
self.remote.transaction()
.offer_cancel("root", m.tx_json.Sequence+2)
.on('proposed', function (m) {
// console.log("ERROR: offer_cancel future: %s", JSON.stringify(m));
callback(m.result !== 'temBAD_SEQUENCE');
})
.submit();
},
// See if ledger_accept will crash.
function (callback) {
self.remote
.once('ledger_closed', function (mesage) {
// console.log("LEDGER_CLOSED: A: %d: %s", ledger_index, ledger_hash);
callback();
})
.ledger_accept();
},
function (callback) {
self.remote
.once('ledger_closed', function (mesage) {
// console.log("LEDGER_CLOSED: B: %d: %s", ledger_index, ledger_hash);
callback();
})
.ledger_accept();
},
function (callback) {
callback();
}
], function (error) {
// console.log("result: error=%s", error);
buster.refute(error);
done();
});
},
"ripple currency conversion : entire offer" :
// mtgox in, XRP out
function (done) {
var self = this;
var seq;
async.waterfall([
function (callback) {
self.what = "Create accounts.";
testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "mtgox"], callback);
},
function (callback) {
self.what = "Owner count 0.";
testutils.verify_owner_count(self.remote, "bob", 0, callback);
},
function (callback) {
self.what = "Set limits.";
testutils.credit_limits(self.remote,
{
"alice" : "100/USD/mtgox",
"bob" : "1000/USD/mtgox"
},
callback);
},
function (callback) {
self.what = "Owner counts after trust.";
testutils.verify_owner_counts(self.remote,
{
"alice" : 1,
"bob" : 1,
},
callback);
},
function (callback) {
self.what = "Distribute funds.";
testutils.payments(self.remote,
{
"mtgox" : "100/USD/alice"
},
callback);
},
function (callback) {
self.what = "Create offer.";
self.remote.transaction()
.offer_create("bob", "100/USD/mtgox", "500")
.on('proposed', function (m) {
// console.log("PROPOSED: offer_create: %s", JSON.stringify(m));
callback(m.result !== 'tesSUCCESS');
seq = m.tx_json.Sequence;
})
.submit();
},
function (callback) {
self.what = "Owner counts after offer create.";
testutils.verify_owner_counts(self.remote,
{
"alice" : 1,
"bob" : 2,
},
callback);
},
function (callback) {
self.what = "Verify offer balance.";
testutils.verify_offer(self.remote, "bob", seq, "100/USD/mtgox", "500", callback);
},
function (callback) {
self.what = "Alice converts USD to XRP.";
self.remote.transaction()
.payment("alice", "alice", "500")
.send_max("100/USD/mtgox")
.on('proposed', function (m) {
// console.log("proposed: %s", JSON.stringify(m));
callback(m.result !== 'tesSUCCESS');
})
.submit();
},
function (callback) {
self.what = "Verify balances.";
testutils.verify_balances(self.remote,
{
"alice" : [ "0/USD/mtgox", String(10000000000+500-2*(Transaction.fees['default'].to_number())) ],
"bob" : "100/USD/mtgox",
},
callback);
},
function (callback) {
self.what = "Verify offer consumed.";
testutils.verify_offer_not_found(self.remote, "bob", seq, callback);
},
function (callback) {
self.what = "Owner counts after consumed.";
testutils.verify_owner_counts(self.remote,
{
"alice" : 1,
"bob" : 1,
},
callback);
},
], function (error) {
buster.refute(error, self.what);
done();
});
},
"ripple currency conversion : offerer into debt" :
// alice in, carol out
function (done) {
var self = this;
var seq;
async.waterfall([
function (callback) {
self.what = "Create accounts.";
testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "carol"], callback);
},
function (callback) {
self.what = "Set limits.";
testutils.credit_limits(self.remote,
{
"alice" : "2000/EUR/carol",
"bob" : "100/USD/alice",
"carol" : "1000/EUR/bob"
},
callback);
},
function (callback) {
self.what = "Create offer to exchange.";
self.remote.transaction()
.offer_create("bob", "50/USD/alice", "200/EUR/carol")
.on('proposed', function (m) {
// console.log("PROPOSED: offer_create: %s", JSON.stringify(m));
callback(m.result !== 'tecUNFUNDED_OFFER');
seq = m.tx_json.Sequence;
})
.submit();
},
// function (callback) {
// self.what = "Alice converts USD to EUR via offer.";
//
// self.remote.transaction()
// .offer_create("alice", "200/EUR/carol", "50/USD/alice")
// .on('proposed', function (m) {
// // console.log("PROPOSED: offer_create: %s", JSON.stringify(m));
// callback(m.result !== 'tesSUCCESS');
//
// seq = m.tx_json.Sequence;
// })
// .submit();
// },
// function (callback) {
// self.what = "Verify balances.";
//
// testutils.verify_balances(self.remote,
// {
// "alice" : [ "-50/USD/bob", "200/EUR/carol" ],
// "bob" : [ "50/USD/alice", "-200/EUR/carol" ],
// "carol" : [ "-200/EUR/alice", "200/EUR/bob" ],
// },
// callback);
// },
// function (callback) {
// self.what = "Verify offer consumed.";
//
// testutils.verify_offer_not_found(self.remote, "bob", seq, callback);
// },
], function (error) {
buster.refute(error, self.what);
done();
});
},
"ripple currency conversion : in parts" :
function (done) {
var self = this;
var seq;
async.waterfall([
function (callback) {
self.what = "Create accounts.";
testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "mtgox"], callback);
},
function (callback) {
self.what = "Set limits.";
testutils.credit_limits(self.remote,
{
"alice" : "200/USD/mtgox",
"bob" : "1000/USD/mtgox"
},
callback);
},
function (callback) {
self.what = "Distribute funds.";
testutils.payments(self.remote,
{
"mtgox" : "200/USD/alice"
},
callback);
},
function (callback) {
self.what = "Create offer.";
self.remote.transaction()
.offer_create("bob", "100/USD/mtgox", "500")
.on('proposed', function (m) {
// console.log("PROPOSED: offer_create: %s", JSON.stringify(m));
callback(m.result !== 'tesSUCCESS');
seq = m.tx_json.Sequence;
})
.submit();
},
function (callback) {
self.what = "Alice converts USD to XRP.";
self.remote.transaction()
.payment("alice", "alice", "200")
.send_max("100/USD/mtgox")
.on('proposed', function (m) {
// console.log("proposed: %s", JSON.stringify(m));
callback(m.result !== 'tesSUCCESS');
})
.submit();
},
function (callback) {
self.what = "Verify offer balance.";
testutils.verify_offer(self.remote, "bob", seq, "60/USD/mtgox", "300", callback);
},
function (callback) {
self.what = "Verify balances.";
testutils.verify_balances(self.remote,
{
"alice" : [ "160/USD/mtgox", String(10000000000+200-2*(Transaction.fees['default'].to_number())) ],
"bob" : "40/USD/mtgox",
},
callback);
},
function (callback) {
self.what = "Alice converts USD to XRP should fail due to PartialPayment.";
self.remote.transaction()
.payment("alice", "alice", "600")
.send_max("100/USD/mtgox")
.on('proposed', function (m) {
// console.log("proposed: %s", JSON.stringify(m));
callback(m.result !== 'tecPATH_PARTIAL');
})
.submit();
},
function (callback) {
self.what = "Alice converts USD to XRP.";
self.remote.transaction()
.payment("alice", "alice", "600")
.send_max("100/USD/mtgox")
.set_flags('PartialPayment')
.on('proposed', function (m) {
// console.log("proposed: %s", JSON.stringify(m));
callback(m.result !== 'tesSUCCESS');
})
.submit();
},
function (callback) {
self.what = "Verify offer consumed.";
testutils.verify_offer_not_found(self.remote, "bob", seq, callback);
},
function (callback) {
self.what = "Verify balances.";
testutils.verify_balances(self.remote,
{
"alice" : [ "100/USD/mtgox", String(10000000000+200+300-4*(Transaction.fees['default'].to_number())) ],
"bob" : "100/USD/mtgox",
},
callback);
},
], function (error) {
buster.refute(error, self.what);
done();
});
},
});
buster.testCase("Offer cross currency", {
'setUp' : testutils.build_setup(),
'tearDown' : testutils.build_teardown(),
"ripple cross currency payment - start with XRP" :
// alice --> [XRP --> carol --> USD/mtgox] --> bob
function (done) {
var self = this;
var seq;
// self.remote.set_trace();
async.waterfall([
function (callback) {
self.what = "Create accounts.";
testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "carol", "mtgox"], callback);
},
function (callback) {
self.what = "Set limits.";
testutils.credit_limits(self.remote,
{
"carol" : "1000/USD/mtgox",
"bob" : "2000/USD/mtgox"
},
callback);
},
function (callback) {
self.what = "Distribute funds.";
testutils.payments(self.remote,
{
"mtgox" : "500/USD/carol"
},
callback);
},
function (callback) {
self.what = "Create offer.";
self.remote.transaction()
.offer_create("carol", "500", "50/USD/mtgox")
.on('proposed', function (m) {
// console.log("PROPOSED: offer_create: %s", JSON.stringify(m));
callback(m.result !== 'tesSUCCESS');
seq = m.tx_json.Sequence;
})
.submit();
},
function (callback) {
self.what = "Alice send USD/mtgox converting from XRP.";
self.remote.transaction()
.payment("alice", "bob", "25/USD/mtgox")
.send_max("333")
.on('proposed', function (m) {
// console.log("proposed: %s", JSON.stringify(m));
callback(m.result !== 'tesSUCCESS');
})
.submit();
},
function (callback) {
self.what = "Verify balances.";
testutils.verify_balances(self.remote,
{
// "alice" : [ "500" ],
"bob" : "25/USD/mtgox",
"carol" : "475/USD/mtgox",
},
callback);
},
function (callback) {
self.what = "Verify offer consumed.";
testutils.verify_offer_not_found(self.remote, "bob", seq, callback);
},
], function (error) {
buster.refute(error, self.what);
done();
});
},
"ripple cross currency payment - end with XRP" :
// alice --> [USD/mtgox --> carol --> XRP] --> bob
function (done) {
var self = this;
var seq;
// self.remote.set_trace();
async.waterfall([
function (callback) {
self.what = "Create accounts.";
testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "carol", "mtgox"], callback);
},
function (callback) {
self.what = "Set limits.";
testutils.credit_limits(self.remote,
{
"alice" : "1000/USD/mtgox",
"carol" : "2000/USD/mtgox"
},
callback);
},
function (callback) {
self.what = "Distribute funds.";
testutils.payments(self.remote,
{
"mtgox" : "500/USD/alice"
},
callback);
},
function (callback) {
self.what = "Create offer.";
self.remote.transaction()
.offer_create("carol", "50/USD/mtgox", "500")
.on('proposed', function (m) {
// console.log("PROPOSED: offer_create: %s", JSON.stringify(m));
callback(m.result !== 'tesSUCCESS');
seq = m.tx_json.Sequence;
})
.submit();
},
function (callback) {
self.what = "Alice send XRP to bob converting from USD/mtgox.";
self.remote.transaction()
.payment("alice", "bob", "250")
.send_max("333/USD/mtgox")
.on('proposed', function (m) {
// console.log("proposed: %s", JSON.stringify(m));
callback(m.result !== 'tesSUCCESS');
})
.submit();
},
function (callback) {
self.what = "Verify balances.";
testutils.verify_balances(self.remote,
{
"alice" : "475/USD/mtgox",
"bob" : "10000000250",
"carol" : "25/USD/mtgox",
},
callback);
},
function (callback) {
self.what = "Verify offer partially consumed.";
testutils.verify_offer(self.remote, "carol", seq, "25/USD/mtgox", "250", callback);
},
], function (error) {
buster.refute(error, self.what);
done();
});
},
"ripple cross currency bridged payment" :
// alice --> [USD/mtgox --> carol --> XRP] --> [XRP --> dan --> EUR/bitstamp] --> bob
function (done) {
var self = this;
var seq_carol;
var seq_dan;
// self.remote.set_trace();
async.waterfall([
function (callback) {
self.what = "Create accounts.";
testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "carol", "dan", "bitstamp", "mtgox"], callback);
},
function (callback) {
self.what = "Set limits.";
testutils.credit_limits(self.remote,
{
"alice" : "1000/USD/mtgox",
"bob" : "1000/EUR/bitstamp",
"carol" : "1000/USD/mtgox",
"dan" : "1000/EUR/bitstamp"
},
callback);
},
function (callback) {
self.what = "Distribute funds.";
testutils.payments(self.remote,
{
"bitstamp" : "400/EUR/dan",
"mtgox" : "500/USD/alice",
},
callback);
},
function (callback) {
self.what = "Create offer carol.";
self.remote.transaction()
.offer_create("carol", "50/USD/mtgox", "500")
.on('proposed', function (m) {
// console.log("PROPOSED: offer_create: %s", JSON.stringify(m));
callback(m.result !== 'tesSUCCESS');
seq_carol = m.tx_json.Sequence;
})
.submit();
},
function (callback) {
self.what = "Create offer dan.";
self.remote.transaction()
.offer_create("dan", "500", "50/EUR/bitstamp")
.on('proposed', function (m) {
// console.log("PROPOSED: offer_create: %s", JSON.stringify(m));
callback(m.result !== 'tesSUCCESS');
seq_dan = m.tx_json.Sequence;
})
.submit();
},
function (callback) {
self.what = "Alice send EUR/bitstamp to bob converting from USD/mtgox.";
self.remote.transaction()
.payment("alice", "bob", "30/EUR/bitstamp")
.send_max("333/USD/mtgox")
.path_add( [ { currency: "XRP" } ])
.on('proposed', function (m) {
// console.log("proposed: %s", JSON.stringify(m));
callback(m.result !== 'tesSUCCESS');
})
.submit();
},
function (callback) {
self.what = "Verify balances.";
testutils.verify_balances(self.remote,
{
"alice" : "470/USD/mtgox",
"bob" : "30/EUR/bitstamp",
"carol" : "30/USD/mtgox",
"dan" : "370/EUR/bitstamp",
},
callback);
},
function (callback) {
self.what = "Verify carol offer partially consumed.";
testutils.verify_offer(self.remote, "carol", seq_carol, "20/USD/mtgox", "200", callback);
},
function (callback) {
self.what = "Verify dan offer partially consumed.";
testutils.verify_offer(self.remote, "dan", seq_dan, "200", "20/EUR/mtgox", callback);
},
], function (error) {
buster.refute(error, self.what);
done();
});
},
});
// vim:sw=2:sts=2:ts=8:et