diff --git a/src/cpp/ripple/NetworkOPs.cpp b/src/cpp/ripple/NetworkOPs.cpp index 92a47fa5fc..aa9bcd9204 100644 --- a/src/cpp/ripple/NetworkOPs.cpp +++ b/src/cpp/ripple/NetworkOPs.cpp @@ -1447,6 +1447,20 @@ void NetworkOPs::unsubAccount(InfoSub* ispListener, const boost::unordered_setgetOrderBookDB().makeBookListeners(currencyIn, currencyOut, issuerIn, issuerOut); + if(listeners) listeners->addSubscriber(ispListener); + return(true); +} + +bool NetworkOPs::unsubBook(InfoSub* ispListener, uint160 currencyIn, uint160 currencyOut, uint160 issuerIn, uint160 issuerOut) +{ + BookListeners::pointer listeners=theApp->getOrderBookDB().getBookListeners(currencyIn, currencyOut, issuerIn, issuerOut); + if(listeners) listeners->removeSubscriber(ispListener); + return(true); +} + void NetworkOPs::newLCL(int proposers, int convergeTime, const uint256& ledgerHash) { assert(convergeTime); diff --git a/src/cpp/ripple/NetworkOPs.h b/src/cpp/ripple/NetworkOPs.h index c03edae49f..ae6bff9b08 100644 --- a/src/cpp/ripple/NetworkOPs.h +++ b/src/cpp/ripple/NetworkOPs.h @@ -284,6 +284,9 @@ public: bool subServer(InfoSub* ispListener, Json::Value& jvResult); bool unsubServer(InfoSub* ispListener); + bool subBook(InfoSub* ispListener, uint160 currencyIn, uint160 currencyOut, uint160 issuerIn, uint160 issuerOut); + bool unsubBook(InfoSub* ispListener, uint160 currencyIn, uint160 currencyOut, uint160 issuerIn, uint160 issuerOut); + bool subTransactions(InfoSub* ispListener); bool unsubTransactions(InfoSub* ispListener); diff --git a/src/cpp/ripple/OrderBook.cpp b/src/cpp/ripple/OrderBook.cpp index 2cf716257e..300ebcd31b 100644 --- a/src/cpp/ripple/OrderBook.cpp +++ b/src/cpp/ripple/OrderBook.cpp @@ -20,4 +20,6 @@ OrderBook::OrderBook(SerializedLedgerEntry::pointer ledgerEntry) mBookBase=Ledger::getBookBase(mCurrencyOut,mIssuerOut,mCurrencyIn,mIssuerIn); } + + // vim:ts=4 diff --git a/src/cpp/ripple/OrderBook.h b/src/cpp/ripple/OrderBook.h index c805040849..813709d3b5 100644 --- a/src/cpp/ripple/OrderBook.h +++ b/src/cpp/ripple/OrderBook.h @@ -14,8 +14,6 @@ class OrderBook uint160 mIssuerIn; uint160 mIssuerOut; - boost::unordered_set mListeners; - //SerializedLedgerEntry::pointer mLedgerEntry; OrderBook(SerializedLedgerEntry::pointer ledgerEntry); // For accounts in a ledger public: @@ -34,9 +32,6 @@ public: // looks through the best offers to see how much it would cost to take the given amount STAmount& getTakePrice(STAmount& takeAmount); - - void addSubscriber(InfoSub* sub); - void removeSubscriber(InfoSub* sub); }; // vim:ts=4 diff --git a/src/cpp/ripple/OrderBookDB.cpp b/src/cpp/ripple/OrderBookDB.cpp index 0f6956f8c5..74faa3b12a 100644 --- a/src/cpp/ripple/OrderBookDB.cpp +++ b/src/cpp/ripple/OrderBookDB.cpp @@ -2,6 +2,7 @@ #include "Log.h" #include +SETUP_LOG(); OrderBookDB::OrderBookDB() { @@ -11,6 +12,10 @@ OrderBookDB::OrderBookDB() // TODO: this would be way faster if we could just look under the order dirs void OrderBookDB::setup(Ledger::pointer ledger) { + mXRPOrders.clear(); + mIssuerMap.clear(); + mKnownMap.clear(); + // walk through the entire ledger looking for orderbook entries uint256 currentIndex=ledger->getFirstLedgerIndex(); while(currentIndex.isNonZero()) @@ -60,9 +65,37 @@ void OrderBookDB::getBooks(const uint160& issuerID, const uint160& currencyID, s } } -OrderBook::pointer OrderBookDB::getBook(uint160 mCurrencyIn, uint160 mCurrencyOut, uint160 mIssuerIn, uint160 mIssuerOut) +BookListeners::pointer OrderBookDB::makeBookListeners(uint160 currencyIn, uint160 currencyOut, uint160 issuerIn, uint160 issuerOut) { + BookListeners::pointer ret=getBookListeners(currencyIn, currencyOut, issuerIn, issuerOut); + if(!ret) + { + ret=BookListeners::pointer(new BookListeners); + mListeners[issuerIn][issuerOut][currencyIn][currencyOut]=ret; + } + return(ret); +} +BookListeners::pointer OrderBookDB::getBookListeners(uint160 currencyIn, uint160 currencyOut, uint160 issuerIn, uint160 issuerOut) +{ + std::map > > >::iterator it0=mListeners.find(issuerIn); + if(it0 != mListeners.end()) + { + std::map > >::iterator it1=(*it0).second.find(issuerOut); + if(it1 != (*it0).second.end()) + { + std::map >::iterator it2=(*it1).second.find(currencyIn); + if(it2 != (*it1).second.end()) + { + std::map::iterator it3=(*it2).second.find(currencyOut); + if(it3 != (*it2).second.end()) + { + return( (*it3).second ); + } + } + } + } + return(BookListeners::pointer()); } /* @@ -80,19 +113,101 @@ OrderBook::pointer OrderBookDB::getBook(uint160 mCurrencyIn, uint160 mCurrencyOu "value" : "1" } } -}*/ +} +"ModifiedNode" : { +"FinalFields" : { +"Account" : "rHTxKLzRbniScyQFGMb3NodmxA848W8dKM", +"BookDirectory" : "407AF8FFDE71114B1981574FDDA9B0334572D56FC579735B4B0BD7A625405555", +"BookNode" : "0000000000000000", +"Flags" : 0, +"OwnerNode" : "0000000000000000", +"Sequence" : 32, +"TakerGets" : "149900000000", +"TakerPays" : { +"currency" : "USD", +"issuer" : "r9vbV3EHvXWjSkeQ6CAcYVPGeq7TuiXY2X", +"value" : "49.96666666666667" +} +}, +"LedgerEntryType" : "Offer", +"LedgerIndex" : "C60F8CC514208FA5F7BD03CF1B64B38B7183CD52318FCBBD3726350D4FE693B0", +"PreviousFields" : { +"TakerGets" : "150000000000", +"TakerPays" : { +"currency" : "USD", +"issuer" : "r9vbV3EHvXWjSkeQ6CAcYVPGeq7TuiXY2X", +"value" : "50" +} +}, +"PreviousTxnID" : "1A6AAE3F1AC5A8A7554A5ABC395D17FED5BF62CD90181AA8E4315EDFED4EDEB3", +"PreviousTxnLgrSeq" : 140734 +} + +*/ +// Based on the meta, send the meta to the streams that are listening +// We need to determine which streams a given meta effects void OrderBookDB::processTxn(const SerializedTransaction& stTxn, TER terResult,TransactionMetaSet::pointer& meta,Json::Value& jvObj) { - // check if this is an offer or an offer cancel or a payment that consumes an offer - //check to see what the meta looks like - BOOST_FOREACH(STObject& node,meta->getNodes()) + if(terResult==tesSUCCESS) { - if(node.getFieldU16(sfLedgerEntryType)==ltOFFER) + // check if this is an offer or an offer cancel or a payment that consumes an offer + //check to see what the meta looks like + BOOST_FOREACH(STObject& node,meta->getNodes()) { + try{ + if(node.getFieldU16(sfLedgerEntryType)==ltOFFER) + { + SField* field=NULL; + if(node.getFName() == sfModifiedNode) + { + field=&sfPreviousFields; + }else if(node.getFName() == sfCreatedNode) + { + field=&sfNewFields; + } + + const STObject* previous = dynamic_cast(node.peekAtPField(*field)); + if(previous) + { + STAmount& takerGets = previous->getFieldAmount(sfTakerGets); + uint160 currencyOut=takerGets.getCurrency(); + uint160 issuerOut=takerGets.getIssuer(); + + STAmount& takerPays = previous->getFieldAmount(sfTakerPays); + uint160 currencyIn=takerPays.getCurrency(); + uint160 issuerIn=takerPays.getIssuer(); + + // determine the OrderBook + BookListeners::pointer book=getBookListeners(currencyIn,currencyOut,issuerIn,issuerOut); + if(book) book->publish(jvObj); + } + } + }catch(...) + { + cLog(lsINFO) << "Fields not found in OrderBookDB::processTxn"; + } } } - +} +void BookListeners::addSubscriber(InfoSub* sub) +{ + mListeners.insert(sub); +} + +void BookListeners::removeSubscriber(InfoSub* sub) +{ + mListeners.erase(sub); +} + +void BookListeners::publish(Json::Value& jvObj) +{ + //Json::Value jvObj=node.getJson(0); + + BOOST_FOREACH(InfoSub* sub,mListeners) + { + sub->send(jvObj); + } } \ No newline at end of file diff --git a/src/cpp/ripple/OrderBookDB.h b/src/cpp/ripple/OrderBookDB.h index dc3983a36e..1461e500c9 100644 --- a/src/cpp/ripple/OrderBookDB.h +++ b/src/cpp/ripple/OrderBookDB.h @@ -1,11 +1,24 @@ #include "Ledger.h" #include "OrderBook.h" +#include + /* we can eventually make this cached and just update it as transactions come in. But for now it is probably faster to just generate it each time */ +class BookListeners +{ + boost::unordered_set mListeners; +public: + typedef boost::shared_ptr pointer; + + void addSubscriber(InfoSub* sub); + void removeSubscriber(InfoSub* sub); + void publish(Json::Value& jvObj); +}; + class OrderBookDB { std::vector mEmptyVector; @@ -13,6 +26,9 @@ class OrderBookDB std::map > mIssuerMap; //std::vector mAllOrderBooks; + // issuerIn, issuerOut, currencyIn, currencyOut + std::map > > > mListeners; + std::map mKnownMap; public: @@ -30,10 +46,11 @@ public: float getPrice(uint160& currencyIn,uint160& currencyOut); - OrderBook::pointer getBook(uint160 mCurrencyIn, uint160 mCurrencyOut, uint160 mIssuerIn, uint160 mIssuerOut); + BookListeners::pointer getBookListeners(uint160 currencyIn, uint160 currencyOut, uint160 issuerIn, uint160 issuerOut); + BookListeners::pointer makeBookListeners(uint160 currencyIn, uint160 currencyOut, uint160 issuerIn, uint160 issuerOut); + // see if this txn effects any orderbook void processTxn(const SerializedTransaction& stTxn, TER terResult,TransactionMetaSet::pointer& meta,Json::Value& jvObj); - }; // vim:ts=4 diff --git a/src/cpp/ripple/Pathfinder.cpp b/src/cpp/ripple/Pathfinder.cpp index efa070a189..eaba5e6688 100644 --- a/src/cpp/ripple/Pathfinder.cpp +++ b/src/cpp/ripple/Pathfinder.cpp @@ -12,8 +12,6 @@ SETUP_LOG(); /* -JED: V IIII - we just need to find a succession of the highest quality paths there until we find enough width Don't do branching within each path @@ -133,11 +131,13 @@ bool Pathfinder::bDefaultPath(const STPath& spPath) // functionality is left to the future. // Pathfinder::Pathfinder(const RippleAddress& uSrcAccountID, const RippleAddress& uDstAccountID, const uint160& uSrcCurrencyID, const uint160& uSrcIssuerID, const STAmount& saDstAmount) - : mSrcAccountID(uSrcAccountID.getAccountID()), mDstAccountID(uDstAccountID.getAccountID()), mDstAmount(saDstAmount), mSrcCurrencyID(uSrcCurrencyID), mSrcIssuerID(uSrcIssuerID), mOrderBook(theApp->getLedgerMaster().getCurrentLedger()) + : mSrcAccountID(uSrcAccountID.getAccountID()), mDstAccountID(uDstAccountID.getAccountID()), mDstAmount(saDstAmount), mSrcCurrencyID(uSrcCurrencyID), mSrcIssuerID(uSrcIssuerID) { mLedger = theApp->getLedgerMaster().getCurrentLedger(); mSrcAmount = STAmount(uSrcCurrencyID, uSrcIssuerID, 1, 0, true); // -1/uSrcIssuerID/uSrcIssuerID + theApp->getOrderBookDB().setup( theApp->getLedgerMaster().getCurrentLedger()); // TODO: have the orderbook update itself rather than rebuild it from scratch each time + // Construct the default path for later comparison. PathState::pointer psDefault = boost::make_shared(mDstAmount, mSrcAmount, mLedger); diff --git a/src/cpp/ripple/RPCHandler.cpp b/src/cpp/ripple/RPCHandler.cpp index e6a245aad6..7f2045779b 100644 --- a/src/cpp/ripple/RPCHandler.cpp +++ b/src/cpp/ripple/RPCHandler.cpp @@ -2449,6 +2449,25 @@ Json::Value RPCHandler::doSubscribe(Json::Value jvRequest) } } + if (jvRequest.isMember("books")) + { + 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(); + + mNetOps->subBook(ispSub,currencyIn,currencyOut,issuerIn,issuerOut); + if((*it)["StateNow"].asBool()) + { + + } + } + } + return jvResult; } @@ -2546,6 +2565,21 @@ Json::Value RPCHandler::doUnsubscribe(Json::Value jvRequest) } } + if (jvRequest.isMember("books")) + { + 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(); + + mNetOps->unsubBook(ispSub,currencyIn,currencyOut,issuerIn,issuerOut); + } + } + return jvResult; } diff --git a/test/send-test.js b/test/send-test.js index d2fe9c7f32..98bf63d0e0 100644 --- a/test/send-test.js +++ b/test/send-test.js @@ -40,8 +40,8 @@ buster.testCase("Fee Changes", { */ buster.testCase("Sending", { - 'setUp' : testutils.build_setup(), - // 'setUp' : testutils.build_setup({verbose: true , no_server: true}), + //'setUp' : testutils.build_setup(), + 'setUp' : testutils.build_setup({verbose: true , no_server: true}), 'tearDown' : testutils.build_teardown(), "send XRP to non-existent account with insufficent fee" : @@ -975,7 +975,7 @@ buster.testCase("Indirect ripple", { }); }, - "indirect ripple with path" : + "=> indirect ripple with path" : function (done) { var self = this;