mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user