rippled
Loading...
Searching...
No Matches
OrderBookDB.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/ledger/LedgerMaster.h>
21#include <xrpld/app/ledger/OrderBookDB.h>
22#include <xrpld/app/main/Application.h>
23#include <xrpld/app/misc/AMMUtils.h>
24#include <xrpld/app/misc/NetworkOPs.h>
25#include <xrpld/core/Config.h>
26#include <xrpld/core/JobQueue.h>
27
28#include <xrpl/basics/Log.h>
29#include <xrpl/protocol/Indexes.h>
30
31namespace ripple {
32
34 : app_(app), seq_(0), j_(app.journal("OrderBookDB"))
35{
36}
37
38void
40{
42 {
43 JLOG(j_.warn()) << "Eliding full order book update: no ledger";
44 return;
45 }
46
47 auto seq = seq_.load();
48
49 if (seq != 0)
50 {
51 if ((ledger->seq() > seq) && ((ledger->seq() - seq) < 25600))
52 return;
53
54 if ((ledger->seq() <= seq) && ((seq - ledger->seq()) < 16))
55 return;
56 }
57
58 if (seq_.exchange(ledger->seq()) != seq)
59 return;
60
61 JLOG(j_.debug()) << "Full order book update: " << seq << " to "
62 << ledger->seq();
63
64 if (app_.config().PATH_SEARCH_MAX != 0)
65 {
66 if (app_.config().standalone())
67 update(ledger);
68 else
71 "OrderBookDB::update: " + std::to_string(ledger->seq()),
72 [this, ledger]() { update(ledger); });
73 }
74}
75
76void
78{
79 if (app_.config().PATH_SEARCH_MAX == 0)
80 return; // pathfinding has been disabled
81
82 // A newer full update job is pending
83 if (auto const seq = seq_.load(); seq > ledger->seq())
84 {
85 JLOG(j_.debug()) << "Eliding update for " << ledger->seq()
86 << " because of pending update to later " << seq;
87 return;
88 }
89
90 decltype(allBooks_) allBooks;
91 decltype(xrpBooks_) xrpBooks;
92 decltype(domainBooks_) domainBooks;
93 decltype(xrpDomainBooks_) xrpDomainBooks;
94
95 allBooks.reserve(allBooks_.size());
96 xrpBooks.reserve(xrpBooks_.size());
97
98 JLOG(j_.debug()) << "Beginning update (" << ledger->seq() << ")";
99
100 // walk through the entire ledger looking for orderbook/AMM entries
101 int cnt = 0;
102
103 try
104 {
105 for (auto& sle : ledger->sles)
106 {
107 if (app_.isStopping())
108 {
109 JLOG(j_.info())
110 << "Update halted because the process is stopping";
111 seq_.store(0);
112 return;
113 }
114
115 if (sle->getType() == ltDIR_NODE &&
116 sle->isFieldPresent(sfExchangeRate) &&
117 sle->getFieldH256(sfRootIndex) == sle->key())
118 {
119 Book book;
120
121 book.in.currency = sle->getFieldH160(sfTakerPaysCurrency);
122 book.in.account = sle->getFieldH160(sfTakerPaysIssuer);
123 book.out.currency = sle->getFieldH160(sfTakerGetsCurrency);
124 book.out.account = sle->getFieldH160(sfTakerGetsIssuer);
125 book.domain = (*sle)[~sfDomainID];
126
127 if (book.domain)
128 domainBooks_[{book.in, *book.domain}].insert(book.out);
129 else
130 allBooks[book.in].insert(book.out);
131
132 if (book.domain && isXRP(book.out))
133 xrpDomainBooks.insert({book.in, *book.domain});
134 else if (isXRP(book.out))
135 xrpBooks.insert(book.in);
136
137 ++cnt;
138 }
139 else if (sle->getType() == ltAMM)
140 {
141 auto const issue1 = (*sle)[sfAsset].get<Issue>();
142 auto const issue2 = (*sle)[sfAsset2].get<Issue>();
143 auto addBook = [&](Issue const& in, Issue const& out) {
144 allBooks[in].insert(out);
145
146 if (isXRP(out))
147 xrpBooks.insert(in);
148
149 ++cnt;
150 };
151 addBook(issue1, issue2);
152 addBook(issue2, issue1);
153 }
154 }
155 }
156 catch (SHAMapMissingNode const& mn)
157 {
158 JLOG(j_.info()) << "Missing node in " << ledger->seq()
159 << " during update: " << mn.what();
160 seq_.store(0);
161 return;
162 }
163
164 JLOG(j_.debug()) << "Update completed (" << ledger->seq() << "): " << cnt
165 << " books found";
166
167 {
169 allBooks_.swap(allBooks);
170 xrpBooks_.swap(xrpBooks);
171 domainBooks_.swap(domainBooks);
172 xrpDomainBooks_.swap(xrpDomainBooks);
173 }
174
176}
177
178void
180{
181 bool toXRP = isXRP(book.out);
182
184
185 if (book.domain)
186 domainBooks_[{book.in, *book.domain}].insert(book.out);
187 else
188 allBooks_[book.in].insert(book.out);
189
190 if (book.domain && toXRP)
191 xrpDomainBooks_.insert({book.in, *book.domain});
192 else if (toXRP)
193 xrpBooks_.insert(book.in);
194}
195
196// return list of all orderbooks that want this issuerID and currencyID
199 Issue const& issue,
200 std::optional<uint256> const& domain)
201{
203
204 {
206
207 auto getBooks = [&](auto const& container, auto const& key) {
208 if (auto it = container.find(key); it != container.end())
209 {
210 auto const& books = it->second;
211 ret.reserve(books.size());
212
213 for (auto const& gets : books)
214 ret.emplace_back(issue, gets, domain);
215 }
216 };
217
218 if (!domain)
219 getBooks(allBooks_, issue);
220 else
221 getBooks(domainBooks_, std::make_pair(issue, *domain));
222 }
223
224 return ret;
225}
226
227int
229 Issue const& issue,
230 std::optional<uint256> const& domain)
231{
233
234 if (!domain)
235 {
236 if (auto it = allBooks_.find(issue); it != allBooks_.end())
237 return static_cast<int>(it->second.size());
238 }
239 else
240 {
241 if (auto it = domainBooks_.find({issue, *domain});
242 it != domainBooks_.end())
243 return static_cast<int>(it->second.size());
244 }
245
246 return 0;
247}
248
249bool
251{
253 if (domain)
254 return xrpDomainBooks_.contains({issue, *domain});
255 return xrpBooks_.contains(issue);
256}
257
260{
262 auto ret = getBookListeners(book);
263
264 if (!ret)
265 {
267
268 mListeners[book] = ret;
269 XRPL_ASSERT(
270 getBookListeners(book) == ret,
271 "ripple::OrderBookDB::makeBookListeners : result roundtrip "
272 "lookup");
273 }
274
275 return ret;
276}
277
280{
283
284 auto it0 = mListeners.find(book);
285 if (it0 != mListeners.end())
286 ret = it0->second;
287
288 return ret;
289}
290
291// Based on the meta, send the meta to the streams that are listening.
292// We need to determine which streams a given meta effects.
293void
296 AcceptedLedgerTx const& alTx,
297 MultiApiJson const& jvObj)
298{
300
301 // For this particular transaction, maintain the set of unique
302 // subscriptions that have already published it. This prevents sending
303 // the transaction multiple times if it touches multiple ltOFFER
304 // entries for the same book, or if it touches multiple books and a
305 // single client has subscribed to those books.
306 hash_set<std::uint64_t> havePublished;
307
308 for (auto const& node : alTx.getMeta().getNodes())
309 {
310 try
311 {
312 if (node.getFieldU16(sfLedgerEntryType) == ltOFFER)
313 {
314 auto process = [&, this](SField const& field) {
315 if (auto data = dynamic_cast<STObject const*>(
316 node.peekAtPField(field));
317 data && data->isFieldPresent(sfTakerPays) &&
318 data->isFieldPresent(sfTakerGets))
319 {
320 auto listeners = getBookListeners(
321 {data->getFieldAmount(sfTakerGets).issue(),
322 data->getFieldAmount(sfTakerPays).issue(),
323 (*data)[~sfDomainID]});
324 if (listeners)
325 listeners->publish(jvObj, havePublished);
326 }
327 };
328
329 // We need a field that contains the TakerGets and TakerPays
330 // parameters.
331 if (node.getFName() == sfModifiedNode)
332 process(sfPreviousFields);
333 else if (node.getFName() == sfCreatedNode)
334 process(sfNewFields);
335 else if (node.getFName() == sfDeletedNode)
336 process(sfFinalFields);
337 }
338 }
339 catch (std::exception const& ex)
340 {
341 JLOG(j_.info())
342 << "processTxn: field not found (" << ex.what() << ")";
343 }
344 }
345}
346
347} // namespace ripple
Stream debug() const
Definition Journal.h:328
Stream info() const
Definition Journal.h:334
Stream warn() const
Definition Journal.h:340
A transaction that is in a closed ledger.
TxMeta const & getMeta() const
virtual Config & config()=0
virtual bool isStopping() const =0
virtual JobQueue & getJobQueue()=0
virtual NetworkOPs & getOPs()=0
virtual LedgerMaster & getLedgerMaster()=0
Specifies an order book.
Definition Book.h:36
Issue in
Definition Book.h:38
Issue out
Definition Book.h:39
std::optional< uint256 > domain
Definition Book.h:40
bool standalone() const
Definition Config.h:336
int PATH_SEARCH_MAX
Definition Config.h:198
A currency issued by an account.
Definition Issue.h:33
AccountID account
Definition Issue.h:36
Currency currency
Definition Issue.h:35
bool addJob(JobType type, std::string const &name, JobHandler &&jobHandler)
Adds a job to the JobQueue.
Definition JobQueue.h:168
virtual bool isNeedNetworkLedger()=0
BookListeners::pointer getBookListeners(Book const &)
void addOrderBook(Book const &)
void processTxn(std::shared_ptr< ReadView const > const &ledger, AcceptedLedgerTx const &alTx, MultiApiJson const &jvObj)
BookListeners::pointer makeBookListeners(Book const &)
std::atomic< std::uint32_t > seq_
Definition OrderBookDB.h:98
OrderBookDB(Application &app)
void update(std::shared_ptr< ReadView const > const &ledger)
int getBookSize(Issue const &, std::optional< Domain > const &domain=std::nullopt)
hardened_hash_map< std::pair< Issue, Domain >, hardened_hash_set< Issue > > domainBooks_
Definition OrderBookDB.h:84
std::recursive_mutex mLock
Definition OrderBookDB.h:92
hash_set< std::pair< Issue, Domain > > xrpDomainBooks_
Definition OrderBookDB.h:90
beast::Journal const j_
BookToListenersMap mListeners
Definition OrderBookDB.h:96
hash_set< Issue > xrpBooks_
Definition OrderBookDB.h:87
hardened_hash_map< Issue, hardened_hash_set< Issue > > allBooks_
Definition OrderBookDB.h:81
void setup(std::shared_ptr< ReadView const > const &ledger)
Application & app_
Definition OrderBookDB.h:78
std::vector< Book > getBooksByTakerPays(Issue const &, std::optional< Domain > const &domain=std::nullopt)
bool isBookToXRP(Issue const &, std::optional< Domain > domain=std::nullopt)
Identifies fields.
Definition SField.h:144
STArray & getNodes()
Definition TxMeta.h:105
T emplace_back(T... args)
T end(T... args)
T exchange(T... args)
T find(T... args)
T is_same_v
T load(T... args)
T make_pair(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
bool isXRP(AccountID const &c)
Definition AccountID.h:90
@ jtUPDATE_PF
Definition Job.h:56
T reserve(T... args)
T store(T... args)
T to_string(T... args)
T what(T... args)