Prevent duplicate txs in book subscription (RIPD-1465):

If an offer transaction touched multiple ledger entries associated with the same
book, that offer transaction would be published multiple times to anyone subscribed
to that book stream.

Fixes #2095.
This commit is contained in:
Brad Chase
2017-04-28 16:36:35 -04:00
committed by Nik Bougalis
parent f2787dc35c
commit aa2ff00485
4 changed files with 252 additions and 37 deletions

View File

@@ -24,35 +24,44 @@
namespace ripple {
void BookListeners::addSubscriber (InfoSub::ref sub)
void
BookListeners::addSubscriber(InfoSub::ref sub)
{
std::lock_guard <std::recursive_mutex> sl (mLock);
mListeners[sub->getSeq ()] = sub;
std::lock_guard<std::recursive_mutex> sl(mLock);
mListeners[sub->getSeq()] = sub;
}
void BookListeners::removeSubscriber (std::uint64_t seq)
void
BookListeners::removeSubscriber(std::uint64_t seq)
{
std::lock_guard <std::recursive_mutex> sl (mLock);
mListeners.erase (seq);
std::lock_guard<std::recursive_mutex> sl(mLock);
mListeners.erase(seq);
}
void BookListeners::publish (Json::Value const& jvObj)
void
BookListeners::publish(
Json::Value const& jvObj,
hash_set<std::uint64_t>& havePublished)
{
std::lock_guard <std::recursive_mutex> sl (mLock);
auto it = mListeners.cbegin ();
std::lock_guard<std::recursive_mutex> sl(mLock);
auto it = mListeners.cbegin();
while (it != mListeners.cend ())
while (it != mListeners.cend())
{
InfoSub::pointer p = it->second.lock ();
InfoSub::pointer p = it->second.lock();
if (p)
{
p->send (jvObj, true);
// Only publish jvObj if this is the first occurence
if(havePublished.emplace(p->getSeq()).second)
{
p->send(jvObj, true);
}
++it;
}
else
it = mListeners.erase (it);
it = mListeners.erase(it);
}
}
} // ripple
} // namespace ripple

View File

@@ -32,11 +32,33 @@ class BookListeners
public:
using pointer = std::shared_ptr<BookListeners>;
BookListeners () {}
BookListeners()
{
}
void addSubscriber (InfoSub::ref sub);
void removeSubscriber (std::uint64_t sub);
void publish (Json::Value const& jvObj);
/** Add a new subscription for this book
*/
void
addSubscriber(InfoSub::ref sub);
/** Stop publishing to a subscriber
*/
void
removeSubscriber(std::uint64_t sub);
/** Publish a transaction to subscribers
Publish a transaction to clients subscribed to changes on this book.
Uses havePublished to prevent sending duplicate transactions to clients
that have subscribed to multiple books.
@param jvObj JSON transaction data to publish
@param havePublished InfoSub sequence numbers that have already
published this transaction.
*/
void
publish(Json::Value const& jvObj, hash_set<std::uint64_t>& havePublished);
private:
std::recursive_mutex mLock;
@@ -44,6 +66,6 @@ private:
hash_map<std::uint64_t, InfoSub::wptr> mListeners;
};
} // ripple
} // namespace ripple
#endif

View File

@@ -239,9 +239,15 @@ void OrderBookDB::processTxn (
const AcceptedLedgerTx& alTx, Json::Value const& jvObj)
{
std::lock_guard <std::recursive_mutex> sl (mLock);
if (alTx.getResult () == tesSUCCESS)
{
// For this particular transaction, maintain the set of unique
// subscriptions that have already published it. This prevents sending
// the transaction multiple times if it touches multiple ltOFFER
// entries for the same book, or if it touches multiple books and a
// single client has subscribed to those books.
hash_set<std::uint64_t> havePublished;
// 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.
@@ -272,12 +278,14 @@ void OrderBookDB::processTxn (
data->isFieldPresent (sfTakerGets))
{
// determine the OrderBook
auto listeners = getBookListeners (
{data->getFieldAmount (sfTakerGets).issue(),
data->getFieldAmount (sfTakerPays).issue()});
Book b{data->getFieldAmount(sfTakerGets).issue(),
data->getFieldAmount(sfTakerPays).issue()};
auto listeners = getBookListeners(b);
if (listeners)
listeners->publish (jvObj);
{
listeners->publish(jvObj, havePublished);
}
}
}
}