mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
Merge branch 'master' of github.com:jedmccaleb/NewCoin
This commit is contained in:
@@ -183,6 +183,7 @@ HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash)
|
||||
|
||||
#ifndef NO_SQLITE3_PREPARE
|
||||
{
|
||||
LoadEvent::autoptr event(theApp->getJobQueue().getLoadEventAP(jtDISK, "HOS::retrieve"));
|
||||
ScopedLock sl(theApp->getHashNodeDB()->getDBLock());
|
||||
static SqliteStatement pSt(theApp->getHashNodeDB()->getDB()->getSqliteDB(),
|
||||
"SELECT ObjType,LedgerIndex,Object FROM CommittedObjects WHERE Hash = ?;");
|
||||
|
||||
@@ -405,7 +405,9 @@ void Ledger::saveAcceptedLedger(Job&, bool fromConsensus)
|
||||
cLog(lsTRACE) << "saveAcceptedLedger " << (fromConsensus ? "fromConsensus " : "fromAcquire ") << getLedgerSeq();
|
||||
static boost::format ledgerExists("SELECT LedgerSeq FROM Ledgers INDEXED BY SeqLedger where LedgerSeq = %u;");
|
||||
static boost::format deleteLedger("DELETE FROM Ledgers WHERE LedgerSeq = %u;");
|
||||
static boost::format AcctTransExists("SELECT LedgerSeq FROM AccountTransactions WHERE TransID = '%s';");
|
||||
static boost::format deleteTrans1("DELETE FROM Transactions WHERE LedgerSeq = %u;");
|
||||
static boost::format deleteTrans2("DELETE FROM AccountTransactions WHERE LedgerSeq = %u;");
|
||||
static boost::format deleteAcctTrans("DELETE FROM AccountTransactions WHERE TransID = '%s';");
|
||||
static boost::format transExists("SELECT Status FROM Transactions WHERE TransID = '%s';");
|
||||
static boost::format
|
||||
updateTx("UPDATE Transactions SET LedgerSeq = %u, Status = '%c', TxnMeta = %s WHERE TransID = '%s';");
|
||||
@@ -430,32 +432,29 @@ void Ledger::saveAcceptedLedger(Job&, bool fromConsensus)
|
||||
|
||||
AcceptedLedger::pointer aLedger = AcceptedLedger::makeAcceptedLedger(shared_from_this());
|
||||
|
||||
{
|
||||
{
|
||||
ScopedLock sl(theApp->getLedgerDB()->getDBLock());
|
||||
if (SQL_EXISTS(theApp->getLedgerDB()->getDB(), boost::str(ledgerExists % mLedgerSeq)))
|
||||
theApp->getLedgerDB()->getDB()->executeSQL(boost::str(deleteLedger % mLedgerSeq));
|
||||
}
|
||||
|
||||
Database *db = theApp->getTxnDB()->getDB();
|
||||
{
|
||||
Database *db = theApp->getTxnDB()->getDB();
|
||||
ScopedLock dbLock(theApp->getTxnDB()->getDBLock());
|
||||
db->executeSQL("BEGIN TRANSACTION;");
|
||||
|
||||
db->executeSQL(boost::str(deleteTrans1 % mLedgerSeq));
|
||||
db->executeSQL(boost::str(deleteTrans2 % mLedgerSeq));
|
||||
|
||||
BOOST_FOREACH(const AcceptedLedger::value_type& vt, aLedger->getMap())
|
||||
{
|
||||
cLog(lsTRACE) << "Saving: " << vt.second.getJson(0);
|
||||
uint256 txID = vt.second.getTransactionID();
|
||||
theApp->getMasterTransaction().inLedger(txID, mLedgerSeq);
|
||||
|
||||
// Make sure transaction is in AccountTransactions.
|
||||
if (!SQL_EXISTS(db, boost::str(AcctTransExists % txID.GetHex())))
|
||||
{
|
||||
// Transaction not in AccountTransactions
|
||||
db->executeSQL(boost::str(deleteAcctTrans % txID.GetHex()));
|
||||
|
||||
const std::vector<RippleAddress>& accts = vt.second.getAffected();
|
||||
if (!accts.empty())
|
||||
{
|
||||
|
||||
std::string sql = "INSERT OR REPLACE INTO AccountTransactions (TransID, Account, LedgerSeq) VALUES ";
|
||||
bool first = true;
|
||||
for (std::vector<RippleAddress>::const_iterator it = accts.begin(), end = accts.end(); it != end; ++it)
|
||||
@@ -480,7 +479,6 @@ void Ledger::saveAcceptedLedger(Job&, bool fromConsensus)
|
||||
}
|
||||
else
|
||||
cLog(lsWARNING) << "Transaction in ledger " << mLedgerSeq << " affects no accounts";
|
||||
}
|
||||
|
||||
if (SQL_EXISTS(db, boost::str(transExists % txID.GetHex())))
|
||||
{
|
||||
@@ -502,9 +500,6 @@ void Ledger::saveAcceptedLedger(Job&, bool fromConsensus)
|
||||
db->executeSQL("COMMIT TRANSACTION;");
|
||||
}
|
||||
|
||||
if (!theConfig.RUN_STANDALONE)
|
||||
theApp->getHashedObjectStore().waitWrite(); // wait until all nodes are written
|
||||
|
||||
{
|
||||
ScopedLock sl(theApp->getLedgerDB()->getDBLock());
|
||||
theApp->getLedgerDB()->getDB()->executeSQL(boost::str(addLedger %
|
||||
@@ -513,7 +508,6 @@ void Ledger::saveAcceptedLedger(Job&, bool fromConsensus)
|
||||
mCloseResolution % mCloseFlags %
|
||||
mAccountHash.GetHex() % mTransHash.GetHex()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!fromConsensus)
|
||||
dropCache();
|
||||
@@ -1313,6 +1307,42 @@ std::vector< std::pair<uint32, uint256> > Ledger::getLedgerHashes()
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Ledger::isValidBook(const uint160& uTakerPaysCurrency, const uint160& uTakerPaysIssuerID,
|
||||
const uint160& uTakerGetsCurrency, const uint160& uTakerGetsIssuerID)
|
||||
{
|
||||
if (uTakerPaysCurrency.isZero())
|
||||
{ // XRP in
|
||||
|
||||
if (uTakerPaysIssuerID.isNonZero()) // XRP cannot have an issuer
|
||||
return false;
|
||||
|
||||
if (uTakerGetsCurrency.isZero()) // XRP to XRP not allowed
|
||||
return false;
|
||||
|
||||
if (uTakerGetsIssuerID.isZero()) // non-XRP must have issuer
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// non-XRP in
|
||||
if (uTakerPaysIssuerID.isZero()) // non-XRP must have issuer
|
||||
return false;
|
||||
|
||||
if (uTakerGetsCurrency.isZero()) // non-XRP to XRP
|
||||
{
|
||||
if (uTakerGetsIssuerID.isNonZero()) // XRP cannot have issuer
|
||||
return false;
|
||||
}
|
||||
else // non-XRP to non-XRP
|
||||
{
|
||||
if ((uTakerPaysCurrency == uTakerGetsCurrency) && (uTakerGetsIssuerID == uTakerGetsIssuerID))
|
||||
return false; // Input and output cannot be identical
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint256 Ledger::getBookBase(const uint160& uTakerPaysCurrency, const uint160& uTakerPaysIssuerID,
|
||||
const uint160& uTakerGetsCurrency, const uint160& uTakerGetsIssuerID)
|
||||
{
|
||||
|
||||
@@ -257,6 +257,9 @@ public:
|
||||
//
|
||||
|
||||
// Order book dirs have a base so we can use next to step through them in quality order.
|
||||
static bool isValidBook(const uint160& uTakerPaysCurrency, const uint160& uTakerPaysIssuerID,
|
||||
const uint160& uTakerGetsCurrency, const uint160& uTakerGetsIssuerID);
|
||||
|
||||
static uint256 getBookBase(const uint160& uTakerPaysCurrency, const uint160& uTakerPaysIssuerID,
|
||||
const uint160& uTakerGetsCurrency, const uint160& uTakerGetsIssuerID);
|
||||
|
||||
|
||||
@@ -1598,21 +1598,21 @@ void NetworkOPs::unsubAccount(uint64 uSeq, const boost::unordered_set<RippleAddr
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkOPs::subBook(InfoSub::ref isrListener, const uint160& currencyIn, const uint160& currencyOut,
|
||||
const uint160& issuerIn, const uint160& issuerOut)
|
||||
bool NetworkOPs::subBook(InfoSub::ref isrListener, const uint160& currencyPays, const uint160& currencyGets,
|
||||
const uint160& issuerPays, const uint160& issuerGets)
|
||||
{
|
||||
BookListeners::pointer listeners =
|
||||
theApp->getOrderBookDB().makeBookListeners(currencyIn, currencyOut, issuerIn, issuerOut);
|
||||
theApp->getOrderBookDB().makeBookListeners(currencyPays, currencyGets, issuerPays, issuerGets);
|
||||
if (listeners)
|
||||
listeners->addSubscriber(isrListener);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NetworkOPs::unsubBook(uint64 uSeq,
|
||||
const uint160& currencyIn, const uint160& currencyOut, const uint160& issuerIn, const uint160& issuerOut)
|
||||
const uint160& currencyPays, const uint160& currencyGets, const uint160& issuerPays, const uint160& issuerGets)
|
||||
{
|
||||
BookListeners::pointer listeners =
|
||||
theApp->getOrderBookDB().getBookListeners(currencyIn, currencyOut, issuerIn, issuerOut);
|
||||
theApp->getOrderBookDB().getBookListeners(currencyPays, currencyGets, issuerPays, issuerGets);
|
||||
if (listeners)
|
||||
listeners->removeSubscriber(uSeq);
|
||||
return true;
|
||||
|
||||
@@ -322,10 +322,10 @@ public:
|
||||
bool subServer(InfoSub::ref ispListener, Json::Value& jvResult);
|
||||
bool unsubServer(uint64 uListener);
|
||||
|
||||
bool subBook(InfoSub::ref ispListener, const uint160& currencyIn, const uint160& currencyOut,
|
||||
const uint160& issuerIn, const uint160& issuerOut);
|
||||
bool unsubBook(uint64 uListener, const uint160& currencyIn, const uint160& currencyOut,
|
||||
const uint160& issuerIn, const uint160& issuerOut);
|
||||
bool subBook(InfoSub::ref ispListener, const uint160& currencyPays, const uint160& currencyGets,
|
||||
const uint160& issuerPays, const uint160& issuerGets);
|
||||
bool unsubBook(uint64 uListener, const uint160& currencyPays, const uint160& currencyGets,
|
||||
const uint160& issuerPays, const uint160& issuerGets);
|
||||
|
||||
bool subTransactions(InfoSub::ref ispListener);
|
||||
bool unsubTransactions(uint64 uListener);
|
||||
|
||||
@@ -93,40 +93,40 @@ void OrderBookDB::getBooks(const uint160& issuerID, const uint160& currencyID, s
|
||||
}
|
||||
}
|
||||
|
||||
BookListeners::pointer OrderBookDB::makeBookListeners(const uint160& currencyIn, const uint160& currencyOut,
|
||||
const uint160& issuerIn, const uint160& issuerOut)
|
||||
BookListeners::pointer OrderBookDB::makeBookListeners(const uint160& currencyPays, const uint160& currencyGets,
|
||||
const uint160& issuerPays, const uint160& issuerGets)
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock sl(mLock);
|
||||
BookListeners::pointer ret = getBookListeners(currencyIn, currencyOut, issuerIn, issuerOut);
|
||||
BookListeners::pointer ret = getBookListeners(currencyPays, currencyGets, issuerPays, issuerGets);
|
||||
if (!ret)
|
||||
{
|
||||
ret = boost::make_shared<BookListeners>();
|
||||
mListeners[issuerIn][issuerOut][currencyIn][currencyOut] = ret;
|
||||
mListeners[issuerPays][issuerGets][currencyPays][currencyGets] = ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
BookListeners::pointer OrderBookDB::getBookListeners(const uint160& currencyIn, const uint160& currencyOut,
|
||||
const uint160& issuerIn, const uint160& issuerOut)
|
||||
BookListeners::pointer OrderBookDB::getBookListeners(const uint160& currencyPays, const uint160& currencyGets,
|
||||
const uint160& issuerPays, const uint160& issuerGets)
|
||||
{
|
||||
BookListeners::pointer ret;
|
||||
boost::recursive_mutex::scoped_lock sl(mLock);
|
||||
|
||||
std::map<uint160, std::map<uint160, std::map<uint160, std::map<uint160, BookListeners::pointer> > > >::iterator
|
||||
it0 = mListeners.find(issuerIn);
|
||||
it0 = mListeners.find(issuerPays);
|
||||
if(it0 == mListeners.end())
|
||||
return ret;
|
||||
|
||||
std::map<uint160, std::map<uint160, std::map<uint160, BookListeners::pointer> > >::iterator
|
||||
it1 = (*it0).second.find(issuerOut);
|
||||
it1 = (*it0).second.find(issuerGets);
|
||||
if(it1 == (*it0).second.end())
|
||||
return ret;
|
||||
|
||||
std::map<uint160, std::map<uint160, BookListeners::pointer> >::iterator it2 = (*it1).second.find(currencyIn);
|
||||
std::map<uint160, std::map<uint160, BookListeners::pointer> >::iterator it2 = (*it1).second.find(currencyPays);
|
||||
if(it2 == (*it1).second.end())
|
||||
return ret;
|
||||
|
||||
std::map<uint160, BookListeners::pointer>::iterator it3 = (*it2).second.find(currencyOut);
|
||||
std::map<uint160, BookListeners::pointer>::iterator it3 = (*it2).second.find(currencyGets);
|
||||
if(it3 == (*it2).second.end())
|
||||
return ret;
|
||||
|
||||
@@ -216,16 +216,16 @@ void OrderBookDB::processTxn(Ledger::ref ledger, const ALTransaction& alTx, Json
|
||||
if (data)
|
||||
{
|
||||
STAmount takerGets = data->getFieldAmount(sfTakerGets);
|
||||
uint160 currencyOut = takerGets.getCurrency();
|
||||
uint160 issuerOut = takerGets.getIssuer();
|
||||
uint160 currencyGets = takerGets.getCurrency();
|
||||
uint160 issuerGets = takerGets.getIssuer();
|
||||
|
||||
STAmount takerPays = data->getFieldAmount(sfTakerPays);
|
||||
uint160 currencyIn = takerPays.getCurrency();
|
||||
uint160 issuerIn = takerPays.getIssuer();
|
||||
uint160 currencyPays = takerPays.getCurrency();
|
||||
uint160 issuerPays = takerPays.getIssuer();
|
||||
|
||||
// determine the OrderBook
|
||||
BookListeners::pointer book =
|
||||
getBookListeners(currencyIn, currencyOut, issuerIn, issuerOut);
|
||||
getBookListeners(currencyPays, currencyGets, issuerPays, issuerGets);
|
||||
if (book)
|
||||
book->publish(jvObj);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class OrderBookDB
|
||||
boost::unordered_map<uint160, std::vector<OrderBook::pointer> > mIssuerMap;
|
||||
//std::vector<OrderBook::pointer> mAllOrderBooks;
|
||||
|
||||
// issuerIn, issuerOut, currencyIn, currencyOut
|
||||
// issuerPays, issuerGets, currencyPays, currencyGets
|
||||
std::map<uint160, std::map<uint160, std::map<uint160, std::map<uint160, BookListeners::pointer> > > > mListeners;
|
||||
|
||||
uint32 mSeq;
|
||||
@@ -56,13 +56,13 @@ public:
|
||||
void getBooks(const uint160& issuerID, const uint160& currencyID, std::vector<OrderBook::pointer>& bookRet);
|
||||
|
||||
// returns the best rate we can find
|
||||
float getPrice(uint160& currencyIn,uint160& currencyOut);
|
||||
float getPrice(uint160& currencyPays,uint160& currencyGets);
|
||||
|
||||
|
||||
BookListeners::pointer getBookListeners(const uint160& currencyIn, const uint160& currencyOut,
|
||||
const uint160& issuerIn, const uint160& issuerOut);
|
||||
BookListeners::pointer makeBookListeners(const uint160& currencyIn, const uint160& currencyOut,
|
||||
const uint160& issuerIn, const uint160& issuerOut);
|
||||
BookListeners::pointer getBookListeners(const uint160& currencyPays, const uint160& currencyGets,
|
||||
const uint160& issuerPays, const uint160& issuerGets);
|
||||
BookListeners::pointer makeBookListeners(const uint160& currencyPays, const uint160& currencyGets,
|
||||
const uint160& issuerPays, const uint160& issuerGets);
|
||||
|
||||
// see if this txn effects any orderbook
|
||||
void processTxn(Ledger::ref ledger, const ALTransaction& alTx, Json::Value& jvObj);
|
||||
|
||||
@@ -168,9 +168,6 @@ void Peer::handleVerifyTimer(const boost::system::error_code& ecResult)
|
||||
else if (ecResult)
|
||||
{
|
||||
cLog(lsINFO) << "Peer verify timer error";
|
||||
|
||||
// Can't do anything sound.
|
||||
abort();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -222,6 +219,7 @@ void Peer::connect(const std::string& strIp, int iPort)
|
||||
{
|
||||
cLog(lsINFO) << "Peer: Connect: Outbound: " << ADDRESS(this) << ": " << mIpPort.first << " " << mIpPort.second;
|
||||
|
||||
boost::recursive_mutex::scoped_lock sl(ioMutex);
|
||||
boost::asio::async_connect(
|
||||
getSocket(),
|
||||
itrEndpoint,
|
||||
|
||||
@@ -2753,36 +2753,100 @@ Json::Value RPCHandler::doSubscribe(Json::Value jvRequest, int& cost)
|
||||
}
|
||||
|
||||
if (jvRequest.isMember("books"))
|
||||
{
|
||||
{ // FIXME: This can crash the server if the parameters to things like getBookPage are invalid
|
||||
for (Json::Value::iterator it = jvRequest["books"].begin(); it != jvRequest["books"].end(); it++)
|
||||
{
|
||||
uint160 currencyOut;
|
||||
STAmount::currencyFromString(currencyOut,(*it)["CurrencyOut"].asString());
|
||||
uint160 issuerOut,issuerIn;
|
||||
if(currencyOut.isNonZero())
|
||||
STAmount::issuerFromString(issuerOut,(*it)["IssuerOut"].asString());
|
||||
uint160 currencyIn;
|
||||
STAmount::currencyFromString(currencyIn,(*it)["CurrencyIn"].asString());
|
||||
if(currencyIn.isNonZero())
|
||||
STAmount::issuerFromString(issuerIn,(*it)["IssuerIn"].asString());
|
||||
uint160 uTakerPaysCurrencyID;
|
||||
uint160 uTakerPaysIssuerID;
|
||||
Json::Value jvTakerPays = (*it)["taker_pays"];
|
||||
|
||||
bool bothSides=false;
|
||||
if((*it).isMember("BothSides") && (*it)["BothSides"].asBool()) bothSides=true;
|
||||
// Parse mandatory currency.
|
||||
if (!jvTakerPays.isMember("currency")
|
||||
|| !STAmount::currencyFromString(uTakerPaysCurrencyID, jvTakerPays["currency"].asString()))
|
||||
{
|
||||
cLog(lsINFO) << "Bad taker_pays currency.";
|
||||
|
||||
mNetOps->subBook(ispSub, currencyIn, currencyOut, issuerIn, issuerOut);
|
||||
if(bothSides) mNetOps->subBook(ispSub, currencyOut, currencyIn, issuerOut, issuerIn);
|
||||
if((*it)["StateNow"].asBool())
|
||||
return rpcError(rpcSRC_CUR_MALFORMED);
|
||||
}
|
||||
// Parse optional issuer.
|
||||
else if (((jvTakerPays.isMember("issuer"))
|
||||
&& (!jvTakerPays["issuer"].isString()
|
||||
|| !STAmount::issuerFromString(uTakerPaysIssuerID, jvTakerPays["issuer"].asString())))
|
||||
// Don't allow illegal issuers.
|
||||
|| (!uTakerPaysCurrencyID != !uTakerPaysIssuerID)
|
||||
|| ACCOUNT_ONE == uTakerPaysIssuerID)
|
||||
{
|
||||
cLog(lsINFO) << "Bad taker_pays issuer.";
|
||||
|
||||
return rpcError(rpcSRC_ISR_MALFORMED);
|
||||
}
|
||||
|
||||
uint160 uTakerGetsCurrencyID;
|
||||
uint160 uTakerGetsIssuerID;
|
||||
Json::Value jvTakerGets = (*it)["taker_gets"];
|
||||
|
||||
// Parse mandatory currency.
|
||||
if (!jvTakerGets.isMember("currency")
|
||||
|| !STAmount::currencyFromString(uTakerGetsCurrencyID, jvTakerGets["currency"].asString()))
|
||||
{
|
||||
cLog(lsINFO) << "Bad taker_pays currency.";
|
||||
|
||||
return rpcError(rpcSRC_CUR_MALFORMED);
|
||||
}
|
||||
// Parse optional issuer.
|
||||
else if (((jvTakerGets.isMember("issuer"))
|
||||
&& (!jvTakerGets["issuer"].isString()
|
||||
|| !STAmount::issuerFromString(uTakerGetsIssuerID, jvTakerGets["issuer"].asString())))
|
||||
// Don't allow illegal issuers.
|
||||
|| (!uTakerGetsCurrencyID != !uTakerGetsIssuerID)
|
||||
|| ACCOUNT_ONE == uTakerGetsIssuerID)
|
||||
{
|
||||
cLog(lsINFO) << "Bad taker_gets issuer.";
|
||||
|
||||
return rpcError(rpcDST_ISR_MALFORMED);
|
||||
}
|
||||
|
||||
if (uTakerPaysCurrencyID == uTakerGetsCurrencyID
|
||||
&& uTakerPaysIssuerID == uTakerGetsIssuerID)
|
||||
{
|
||||
cLog(lsINFO) << "taker_gets same as taker_pays.";
|
||||
|
||||
return rpcError(rpcBAD_MARKET);
|
||||
}
|
||||
|
||||
RippleAddress raTakerID;
|
||||
|
||||
if (!(*it).isMember("taker"))
|
||||
{
|
||||
raTakerID.setAccountID(ACCOUNT_ONE);
|
||||
}
|
||||
else if (!raTakerID.setAccountID((*it)["taker"].asString()))
|
||||
{
|
||||
return rpcError(rpcBAD_ISSUER);
|
||||
}
|
||||
|
||||
bool bothSides = (*it)["both_sides"].asBool();
|
||||
|
||||
if (!Ledger::isValidBook(uTakerPaysCurrencyID, uTakerPaysIssuerID, uTakerGetsCurrencyID, uTakerGetsIssuerID))
|
||||
{
|
||||
cLog(lsWARNING) << "Bad market: " <<
|
||||
uTakerPaysCurrencyID << ":" << uTakerPaysIssuerID << " -> " <<
|
||||
uTakerGetsCurrencyID << ":" << uTakerGetsIssuerID;
|
||||
return rpcError(rpcBAD_MARKET);
|
||||
}
|
||||
|
||||
mNetOps->subBook(ispSub, uTakerPaysCurrencyID, uTakerGetsCurrencyID, uTakerPaysIssuerID, uTakerGetsIssuerID);
|
||||
if (bothSides) mNetOps->subBook(ispSub, uTakerGetsCurrencyID, uTakerPaysCurrencyID, uTakerGetsIssuerID, uTakerPaysIssuerID);
|
||||
if ((*it)["state_now"].asBool())
|
||||
{
|
||||
Ledger::pointer ledger= theApp->getLedgerMaster().getClosedLedger();
|
||||
RippleAddress raTakerID;
|
||||
raTakerID.setAccountID(ACCOUNT_ONE);
|
||||
const Json::Value jvMarker = Json::Value(Json::nullValue);
|
||||
mNetOps->getBookPage(ledger, currencyOut, issuerOut, currencyIn, issuerIn, raTakerID.getAccountID(), false, 0, jvMarker, jvResult);
|
||||
mNetOps->getBookPage(ledger, uTakerPaysCurrencyID, uTakerPaysIssuerID, uTakerGetsCurrencyID, uTakerGetsIssuerID, raTakerID.getAccountID(), false, 0, jvMarker, jvResult);
|
||||
if (bothSides)
|
||||
{
|
||||
Json::Value tempJson(Json::objectValue);
|
||||
if (jvResult.isMember("offers")) jvResult["bids"]=jvResult["offers"];
|
||||
mNetOps->getBookPage(ledger, currencyIn, issuerIn, currencyOut, issuerOut, raTakerID.getAccountID(), false, 0, jvMarker, tempJson);
|
||||
mNetOps->getBookPage(ledger, uTakerGetsCurrencyID, uTakerGetsIssuerID, uTakerPaysCurrencyID, uTakerPaysIssuerID, raTakerID.getAccountID(), false, 0, jvMarker, tempJson);
|
||||
if (tempJson.isMember("offers")) jvResult["asks"]=tempJson["offers"];
|
||||
}
|
||||
}
|
||||
@@ -2888,14 +2952,68 @@ Json::Value RPCHandler::doUnsubscribe(Json::Value jvRequest, int& cost)
|
||||
{
|
||||
for (Json::Value::iterator it = jvRequest["books"].begin(); it != jvRequest["books"].end(); it++)
|
||||
{
|
||||
uint160 currencyOut;
|
||||
STAmount::issuerFromString(currencyOut,(*it)["CurrencyOut"].asString());
|
||||
uint160 issuerOut = RippleAddress::createNodePublic( (*it)["IssuerOut"].asString() ).getAccountID();
|
||||
uint160 currencyIn;
|
||||
STAmount::issuerFromString(currencyOut,(*it)["CurrencyIn"].asString());
|
||||
uint160 issuerIn = RippleAddress::createNodePublic( (*it)["IssuerIn"].asString() ).getAccountID();
|
||||
uint160 uTakerPaysCurrencyID;
|
||||
uint160 uTakerPaysIssuerID;
|
||||
Json::Value jvTakerPays = (*it)["taker_pays"];
|
||||
|
||||
mNetOps->unsubBook(ispSub->getSeq(), currencyIn, currencyOut, issuerIn, issuerOut);
|
||||
// Parse mandatory currency.
|
||||
if (!jvTakerPays.isMember("currency")
|
||||
|| !STAmount::currencyFromString(uTakerPaysCurrencyID, jvTakerPays["currency"].asString()))
|
||||
{
|
||||
cLog(lsINFO) << "Bad taker_pays currency.";
|
||||
|
||||
return rpcError(rpcSRC_CUR_MALFORMED);
|
||||
}
|
||||
// Parse optional issuer.
|
||||
else if (((jvTakerPays.isMember("issuer"))
|
||||
&& (!jvTakerPays["issuer"].isString()
|
||||
|| !STAmount::issuerFromString(uTakerPaysIssuerID, jvTakerPays["issuer"].asString())))
|
||||
// Don't allow illegal issuers.
|
||||
|| (!uTakerPaysCurrencyID != !uTakerPaysIssuerID)
|
||||
|| ACCOUNT_ONE == uTakerPaysIssuerID)
|
||||
{
|
||||
cLog(lsINFO) << "Bad taker_pays issuer.";
|
||||
|
||||
return rpcError(rpcSRC_ISR_MALFORMED);
|
||||
}
|
||||
|
||||
uint160 uTakerGetsCurrencyID;
|
||||
uint160 uTakerGetsIssuerID;
|
||||
Json::Value jvTakerGets = (*it)["taker_gets"];
|
||||
|
||||
// Parse mandatory currency.
|
||||
if (!jvTakerGets.isMember("currency")
|
||||
|| !STAmount::currencyFromString(uTakerGetsCurrencyID, jvTakerGets["currency"].asString()))
|
||||
{
|
||||
cLog(lsINFO) << "Bad taker_pays currency.";
|
||||
|
||||
return rpcError(rpcSRC_CUR_MALFORMED);
|
||||
}
|
||||
// Parse optional issuer.
|
||||
else if (((jvTakerGets.isMember("issuer"))
|
||||
&& (!jvTakerGets["issuer"].isString()
|
||||
|| !STAmount::issuerFromString(uTakerGetsIssuerID, jvTakerGets["issuer"].asString())))
|
||||
// Don't allow illegal issuers.
|
||||
|| (!uTakerGetsCurrencyID != !uTakerGetsIssuerID)
|
||||
|| ACCOUNT_ONE == uTakerGetsIssuerID)
|
||||
{
|
||||
cLog(lsINFO) << "Bad taker_gets issuer.";
|
||||
|
||||
return rpcError(rpcDST_ISR_MALFORMED);
|
||||
}
|
||||
|
||||
if (uTakerPaysCurrencyID == uTakerGetsCurrencyID
|
||||
&& uTakerPaysIssuerID == uTakerGetsIssuerID)
|
||||
{
|
||||
cLog(lsINFO) << "taker_gets same as taker_pays.";
|
||||
|
||||
return rpcError(rpcBAD_MARKET);
|
||||
}
|
||||
|
||||
bool bothSides = (*it)["both_sides"].asBool();
|
||||
|
||||
mNetOps->unsubBook(ispSub->getSeq(), uTakerPaysCurrencyID, uTakerGetsCurrencyID, uTakerPaysIssuerID, uTakerGetsIssuerID);
|
||||
if (bothSides) mNetOps->unsubBook(ispSub->getSeq(), uTakerGetsCurrencyID, uTakerPaysCurrencyID, uTakerGetsIssuerID, uTakerPaysIssuerID);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@ std::string SerializedTransaction::getSQLInsertReplaceHeader()
|
||||
|
||||
std::string SerializedTransaction::getMetaSQLInsertHeader()
|
||||
{
|
||||
return "INSERT OR REPLACE INTO Transactions " + getMetaSQLValueHeader() + " VALUES ";
|
||||
return "INSERT INTO Transactions " + getMetaSQLValueHeader() + " VALUES ";
|
||||
}
|
||||
|
||||
std::string SerializedTransaction::getSQL(uint32 inLedger, char status) const
|
||||
|
||||
@@ -128,6 +128,22 @@ Account.prototype.entry = function (callback)
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify object of a relevant transaction.
|
||||
*
|
||||
* This is only meant to be called by the Remote class. You should never have to
|
||||
* call this yourself.
|
||||
*/
|
||||
Account.prototype.notifyTx = function (message)
|
||||
{
|
||||
// Only trigger the event if the account object is actually
|
||||
// subscribed - this prevents some weird phantom events from
|
||||
// occurring.
|
||||
if (this._subs) {
|
||||
this.emit('transaction', message);
|
||||
}
|
||||
};
|
||||
|
||||
exports.Account = Account;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
@@ -98,6 +98,8 @@ Amount.prototype.abs = function () {
|
||||
Amount.prototype.add = function (v) {
|
||||
var result;
|
||||
|
||||
v = Amount.from_json(v);
|
||||
|
||||
if (!this.is_comparable(v)) {
|
||||
result = Amount.NaN();
|
||||
}
|
||||
@@ -779,7 +781,7 @@ Amount.prototype.set_issuer = function (issuer) {
|
||||
// Result in terms of this' currency and issuer.
|
||||
Amount.prototype.subtract = function (v) {
|
||||
// Correctness over speed, less code has less bugs, reuse add code.
|
||||
return this.add(v.negate());
|
||||
return this.add(Amount.from_json(v).negate());
|
||||
};
|
||||
|
||||
Amount.prototype.to_number = function (allow_nan) {
|
||||
|
||||
@@ -22,9 +22,9 @@ Currency.json_rewrite = function (j) {
|
||||
};
|
||||
|
||||
Currency.from_json = function (j) {
|
||||
return 'string' === typeof j
|
||||
? (new Currency()).parse_json(j)
|
||||
: j.clone();
|
||||
if (j instanceof Currency) return j.clone();
|
||||
else if ('string' === typeof j) return (new Currency()).parse_json(j);
|
||||
else return new Currency(); // NaN
|
||||
};
|
||||
|
||||
Currency.is_valid = function (j) {
|
||||
@@ -67,7 +67,7 @@ Currency.prototype.is_native = function () {
|
||||
};
|
||||
|
||||
Currency.prototype.is_valid = function () {
|
||||
return !isNaN(this._value);
|
||||
return 'string' === typeof this._value || !isNaN(this._value);
|
||||
};
|
||||
|
||||
Currency.prototype.to_json = function () {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
exports.Remote = require('./remote').Remote;
|
||||
exports.Amount = require('./amount').Amount;
|
||||
exports.Currency = require('./currency').Currency;
|
||||
exports.UInt160 = require('./amount').UInt160;
|
||||
exports.Seed = require('./amount').Seed;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
var extend = require('extend');
|
||||
var utils = require('./utils');
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var Amount = require('./amount').Amount;
|
||||
|
||||
@@ -102,7 +103,35 @@ Meta.prototype.getAffectedAccounts = function ()
|
||||
}
|
||||
});
|
||||
|
||||
accounts = utils.arrayUnique(accounts);
|
||||
|
||||
return accounts;
|
||||
};
|
||||
|
||||
Meta.prototype.getAffectedBooks = function ()
|
||||
{
|
||||
var books = [];
|
||||
|
||||
this.each(function (an) {
|
||||
if (an.entryType !== 'Offer') return;
|
||||
|
||||
var gets = Amount.from_json(an.fields.TakerGets);
|
||||
var pays = Amount.from_json(an.fields.TakerPays);
|
||||
|
||||
var getsKey = gets.currency().to_json();
|
||||
if (getsKey !== 'XRP') getsKey += '/' + gets.issuer().to_json();
|
||||
|
||||
var paysKey = pays.currency().to_json();
|
||||
if (paysKey !== 'XRP') paysKey += '/' + pays.issuer().to_json();
|
||||
|
||||
var key = getsKey + ":" + paysKey;
|
||||
|
||||
books.push(key);
|
||||
});
|
||||
|
||||
books = utils.arrayUnique(books);
|
||||
|
||||
return books;
|
||||
};
|
||||
|
||||
exports.Meta = Meta;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// Routines for working with an orderbook.
|
||||
//
|
||||
// One OrderBook object represents one half of an order book. (i.e. bids OR
|
||||
// asks) Which one depends on the ordering of the parameters.
|
||||
//
|
||||
// Events:
|
||||
// - transaction A transaction that affects the order book.
|
||||
|
||||
// var network = require("./network.js");
|
||||
|
||||
@@ -12,28 +16,29 @@ var Currency = require('./currency').Currency;
|
||||
var extend = require('extend');
|
||||
|
||||
var OrderBook = function (remote,
|
||||
currency_out, issuer_out,
|
||||
currency_in, issuer_in) {
|
||||
currency_gets, issuer_gets,
|
||||
currency_pays, issuer_pays) {
|
||||
var self = this;
|
||||
|
||||
this._remote = remote;
|
||||
this._currency_out = currency_out;
|
||||
this._issuer_out = issuer_out;
|
||||
this._currency_in = currency_in;
|
||||
this._issuer_in = issuer_in;
|
||||
this._currency_gets = currency_gets;
|
||||
this._issuer_gets = issuer_gets;
|
||||
this._currency_pays = currency_pays;
|
||||
this._issuer_pays = issuer_pays;
|
||||
|
||||
this._subs = 0;
|
||||
|
||||
// Ledger entry object
|
||||
// Important: This must never be overwritten, only extend()-ed
|
||||
this._entry = {};
|
||||
// We consider ourselves synchronized if we have a current copy of the offers,
|
||||
// we are online and subscribed to updates.
|
||||
this._sync = false;
|
||||
|
||||
// Offers
|
||||
this._offers = [];
|
||||
|
||||
this.on('newListener', function (type, listener) {
|
||||
if (OrderBook.subscribe_events.indexOf(type) !== -1) {
|
||||
if (!self._subs && 'open' === self._remote._online_state) {
|
||||
self._remote.request_subscribe()
|
||||
.books([self.to_json()], true)
|
||||
.request();
|
||||
self._subscribe();
|
||||
}
|
||||
self._subs += 1;
|
||||
}
|
||||
@@ -44,6 +49,7 @@ var OrderBook = function (remote,
|
||||
self._subs -= 1;
|
||||
|
||||
if (!self._subs && 'open' === self._remote._online_state) {
|
||||
self._sync = false;
|
||||
self._remote.request_unsubscribe()
|
||||
.books([self.to_json()])
|
||||
.request();
|
||||
@@ -59,6 +65,10 @@ var OrderBook = function (remote,
|
||||
}
|
||||
});
|
||||
|
||||
this._remote.on('disconnect', function () {
|
||||
self._sync = false;
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -67,17 +77,42 @@ OrderBook.prototype = new EventEmitter;
|
||||
/**
|
||||
* List of events that require a remote subscription to the orderbook.
|
||||
*/
|
||||
OrderBook.subscribe_events = ['transaction'];
|
||||
OrderBook.subscribe_events = ['transaction', 'model', 'trade'];
|
||||
|
||||
/**
|
||||
* Subscribes to orderbook.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
OrderBook.prototype._subscribe = function ()
|
||||
{
|
||||
var self = this;
|
||||
self._remote.request_subscribe()
|
||||
.books([self.to_json()], true)
|
||||
.on('error', function () {
|
||||
// XXX What now?
|
||||
})
|
||||
.on('success', function (res) {
|
||||
self._sync = true;
|
||||
self._offers = res.offers;
|
||||
self.emit('model', self._offers);
|
||||
})
|
||||
.request();
|
||||
};
|
||||
|
||||
OrderBook.prototype.to_json = function ()
|
||||
{
|
||||
var json = {
|
||||
"CurrencyOut": this._currency_out,
|
||||
"CurrencyIn": this._currency_in
|
||||
"taker_gets": {
|
||||
"currency": this._currency_gets
|
||||
},
|
||||
"taker_pays": {
|
||||
"currency": this._currency_pays
|
||||
}
|
||||
};
|
||||
|
||||
if (json["CurrencyOut"] !== "XRP") json["IssuerOut"] = this._issuer_out;
|
||||
if (json["CurrencyIn"] !== "XRP") json["IssuerIn"] = this._issuer_in;
|
||||
if (this._currency_gets !== "XRP") json["taker_gets"]["issuer"] = this._issuer_gets;
|
||||
if (this._currency_pays !== "XRP") json["taker_pays"]["issuer"] = this._issuer_pays;
|
||||
|
||||
return json;
|
||||
};
|
||||
@@ -90,15 +125,121 @@ OrderBook.prototype.to_json = function ()
|
||||
*/
|
||||
OrderBook.prototype.is_valid = function ()
|
||||
{
|
||||
// XXX Should check for same currency (non-native) && same issuer
|
||||
return (
|
||||
Currency.is_valid(this._currency_in) &&
|
||||
(this._currency_in !== "XRP" && UInt160.is_valid(this._issuer_in)) &&
|
||||
Currency.is_valid(this._currency_out) &&
|
||||
(this._currency_out !== "XRP" && UInt160.is_valid(this._issuer_out)) &&
|
||||
!(this._currency_in === "XRP" && this._currency_out === "XRP")
|
||||
Currency.is_valid(this._currency_pays) &&
|
||||
(this._currency_pays === "XRP" || UInt160.is_valid(this._issuer_pays)) &&
|
||||
Currency.is_valid(this._currency_gets) &&
|
||||
(this._currency_gets === "XRP" || UInt160.is_valid(this._issuer_gets)) &&
|
||||
!(this._currency_pays === "XRP" && this._currency_gets === "XRP")
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify object of a relevant transaction.
|
||||
*
|
||||
* This is only meant to be called by the Remote class. You should never have to
|
||||
* call this yourself.
|
||||
*/
|
||||
OrderBook.prototype.notifyTx = function (message)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
var changed = false;
|
||||
|
||||
var trade_gets = Amount.from_json("0" + ((this._currency_gets === 'XRP') ? "" :
|
||||
"/" + this._currency_gets +
|
||||
"/" + this._issuer_gets));
|
||||
var trade_pays = Amount.from_json("0" + ((this._currency_pays === 'XRP') ? "" :
|
||||
"/" + this._currency_pays +
|
||||
"/" + this._issuer_pays));
|
||||
|
||||
message.mmeta.each(function (an) {
|
||||
if (an.entryType !== 'Offer') return;
|
||||
|
||||
var i, l, offer;
|
||||
if (an.diffType === 'DeletedNode' ||
|
||||
an.diffType === 'ModifiedNode') {
|
||||
for (i = 0, l = self._offers.length; i < l; i++) {
|
||||
offer = self._offers[i];
|
||||
if (offer.index === an.ledgerIndex) {
|
||||
if (an.diffType === 'DeletedNode') {
|
||||
self._offers.splice(i, 1);
|
||||
}
|
||||
else extend(offer, an.fieldsFinal);
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
trade_gets = trade_gets.add(an.fieldsPrev.TakerGets);
|
||||
trade_pays = trade_pays.add(an.fieldsPrev.TakerPays);
|
||||
if (an.diffType === 'ModifiedNode') {
|
||||
trade_gets = trade_gets.subtract(an.fieldsFinal.TakerGets);
|
||||
trade_pays = trade_pays.subtract(an.fieldsFinal.TakerPays);
|
||||
}
|
||||
} else if (an.diffType === 'CreatedNode') {
|
||||
var price = Amount.from_json(an.fields.TakerPays).ratio_human(an.fields.TakerGets);
|
||||
for (i = 0, l = self._offers.length; i < l; i++) {
|
||||
offer = self._offers[i];
|
||||
var priceItem = Amount.from_json(offer.TakerPays).ratio_human(offer.TakerGets);
|
||||
|
||||
if (price.compareTo(priceItem) <= 0) {
|
||||
var obj = an.fields;
|
||||
obj.index = an.ledgerIndex;
|
||||
self._offers.splice(i, 0, an.fields);
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Only trigger the event if the account object is actually
|
||||
// subscribed - this prevents some weird phantom events from
|
||||
// occurring.
|
||||
if (this._subs) {
|
||||
this.emit('transaction', message);
|
||||
if (changed) this.emit('model', this._offers);
|
||||
if (!trade_gets.is_zero()) this.emit('trade', trade_pays, trade_gets);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get offers model asynchronously.
|
||||
*
|
||||
* This function takes a callback and calls it with an array containing the
|
||||
* current set of offers in this order book.
|
||||
*
|
||||
* If the data is available immediately, the callback may be called synchronously.
|
||||
*/
|
||||
OrderBook.prototype.offers = function (callback)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
if ("function" === typeof callback) {
|
||||
if (this._sync) {
|
||||
callback(this._offers);
|
||||
} else {
|
||||
this.once('model', function (offers) {
|
||||
callback(offers);
|
||||
});
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return latest known offers.
|
||||
*
|
||||
* Usually, this will just be an empty array if the order book hasn't been
|
||||
* loaded yet. But this accessor may be convenient in some circumstances.
|
||||
*/
|
||||
OrderBook.prototype.offersSync = function ()
|
||||
{
|
||||
return this._offers;
|
||||
};
|
||||
|
||||
exports.OrderBook = OrderBook;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
@@ -208,20 +208,25 @@ Request.prototype.books = function (books, state) {
|
||||
|
||||
for (var i = 0, l = books.length; i < l; i++) {
|
||||
var book = books[i];
|
||||
var json = {};
|
||||
|
||||
var json = {
|
||||
"CurrencyOut": Currency.json_rewrite(book["CurrencyOut"]),
|
||||
"CurrencyIn": Currency.json_rewrite(book["CurrencyIn"])
|
||||
};
|
||||
function process(side) {
|
||||
if (!book[side]) throw new Error("Missing "+side);
|
||||
|
||||
if (json["CurrencyOut"] !== "XRP") {
|
||||
json["IssuerOut"] = UInt160.json_rewrite(book["IssuerOut"]);
|
||||
}
|
||||
if (json["CurrencyIn"] !== "XRP") {
|
||||
json["IssuerIn"] = UInt160.json_rewrite(book["IssuerIn"]);
|
||||
var obj = {};
|
||||
obj["currency"] = Currency.json_rewrite(book[side]["currency"]);
|
||||
if (obj["currency"] !== "XRP") {
|
||||
obj.issuer = UInt160.json_rewrite(book[side]["issuer"]);
|
||||
}
|
||||
|
||||
if (state || book["StateNow"]) json["StateNow"] = true;
|
||||
json[side] = obj;
|
||||
}
|
||||
|
||||
process("taker_gets");
|
||||
process("taker_pays");
|
||||
|
||||
if (state || book["state_now"]) json["state_now"] = true;
|
||||
if (book["both_sides"]) json["both_sides"] = true;
|
||||
|
||||
procBooks.push(json);
|
||||
}
|
||||
@@ -280,6 +285,7 @@ var Remote = function (opts, trace) {
|
||||
this._reserve_base = undefined;
|
||||
this._reserve_inc = undefined;
|
||||
this._server_status = undefined;
|
||||
this._last_tx = null;
|
||||
|
||||
// Local signing implies local fees and sequences
|
||||
if (this.local_signing) {
|
||||
@@ -300,6 +306,9 @@ var Remote = function (opts, trace) {
|
||||
// Hash map of Account objects by AccountId.
|
||||
this._accounts = {};
|
||||
|
||||
// Hash map of OrderBook objects
|
||||
this._books = {};
|
||||
|
||||
// List of secrets that we know about.
|
||||
this.secrets = {
|
||||
// Secrets can be set by calling set_secret(account, secret).
|
||||
@@ -629,7 +638,10 @@ Remote.prototype._connect_message = function (ws, json) {
|
||||
// unsubscribes will be added as needed.
|
||||
// XXX If not trusted, need proof.
|
||||
|
||||
// XXX Should de-duplicate transaction events
|
||||
// De-duplicate transactions that are immediately following each other
|
||||
// XXX Should have a cache of n txs so we can dedup out of order txs
|
||||
if (this._last_tx === message.transaction.hash) break;
|
||||
this._last_tx = message.transaction.hash;
|
||||
|
||||
if (this.trace) utils.logObject("remote: tx: %s", message);
|
||||
|
||||
@@ -641,12 +653,15 @@ Remote.prototype._connect_message = function (ws, json) {
|
||||
for (var i = 0, l = affected.length; i < l; i++) {
|
||||
var account = self._accounts[affected[i]];
|
||||
|
||||
// Only trigger the event if the account object is actually
|
||||
// subscribed - this prevents some weird phantom events from
|
||||
// occurring.
|
||||
if (account && account._subs) {
|
||||
account.emit('transaction', message);
|
||||
if (account) account.notifyTx(message);
|
||||
}
|
||||
|
||||
// Pass the event on to any related OrderBooks
|
||||
affected = message.mmeta.getAffectedBooks();
|
||||
for (i = 0, l = affected.length; i < l; i++) {
|
||||
var book = self._books[affected[i]];
|
||||
|
||||
if (book) book.notifyTx(message);
|
||||
}
|
||||
|
||||
this.emit('transaction', message);
|
||||
@@ -1143,13 +1158,26 @@ Remote.prototype.account = function (accountId) {
|
||||
return this._accounts[accountId];
|
||||
};
|
||||
|
||||
Remote.prototype.book = function (currency_out, issuer_out,
|
||||
currency_in, issuer_in) {
|
||||
var book = new OrderBook(this,
|
||||
currency_out, issuer_out,
|
||||
currency_in, issuer_in);
|
||||
Remote.prototype.book = function (currency_gets, issuer_gets,
|
||||
currency_pays, issuer_pays) {
|
||||
var gets = currency_gets;
|
||||
if (gets !== 'XRP') gets += '/' + issuer_gets;
|
||||
var pays = currency_pays;
|
||||
if (pays !== 'XRP') pays += '/' + issuer_pays;
|
||||
|
||||
return book;
|
||||
var key = gets + ":" + pays;
|
||||
|
||||
if (!this._books[key]) {
|
||||
var book = new OrderBook(this,
|
||||
currency_gets, issuer_gets,
|
||||
currency_pays, issuer_pays);
|
||||
|
||||
if (!book.is_valid()) return book;
|
||||
|
||||
this._books[key] = book;
|
||||
}
|
||||
|
||||
return this._books[key];
|
||||
}
|
||||
|
||||
// Return the next account sequence if possible.
|
||||
|
||||
@@ -97,6 +97,21 @@ var assert = function (assertion, msg) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return unique values in array.
|
||||
*/
|
||||
var arrayUnique = function (arr) {
|
||||
var u = {}, a = [];
|
||||
for (var i = 0, l = arr.length; i < l; ++i){
|
||||
if (u.hasOwnProperty(arr[i])) {
|
||||
continue;
|
||||
}
|
||||
a.push(arr[i]);
|
||||
u[arr[i]] = 1;
|
||||
}
|
||||
return a;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a ripple epoch to a JavaScript timestamp.
|
||||
*
|
||||
@@ -115,6 +130,7 @@ exports.stringToHex = stringToHex;
|
||||
exports.chunkString = chunkString;
|
||||
exports.logObject = logObject;
|
||||
exports.assert = assert;
|
||||
exports.arrayUnique = arrayUnique;
|
||||
exports.toTimestamp = toTimestamp;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
Reference in New Issue
Block a user