xrpld
Loading...
Searching...
No Matches
OrderBookDBImpl.cpp
1#include <xrpld/app/ledger/OrderBookDBImpl.h>
2
3#include <xrpld/app/ledger/LedgerMaster.h>
4
5#include <xrpl/basics/Log.h>
6#include <xrpl/basics/base_uint.h>
7#include <xrpl/beast/utility/instrumentation.h>
8#include <xrpl/core/Job.h>
9#include <xrpl/core/JobQueue.h>
10#include <xrpl/core/ServiceRegistry.h>
11#include <xrpl/ledger/AcceptedLedgerTx.h>
12#include <xrpl/ledger/OrderBookDB.h>
13#include <xrpl/ledger/ReadView.h>
14#include <xrpl/protocol/Asset.h>
15#include <xrpl/protocol/Book.h>
16#include <xrpl/protocol/Issue.h>
17#include <xrpl/protocol/LedgerFormats.h>
18#include <xrpl/protocol/SField.h>
19#include <xrpl/protocol/UintTypes.h>
20#include <xrpl/server/NetworkOPs.h>
21#include <xrpl/shamap/SHAMapMissingNode.h>
22
23#include <exception>
24#include <memory>
25#include <mutex>
26#include <optional>
27#include <string>
28#include <utility>
29#include <vector>
30
31namespace xrpl {
32
34 : registry_(registry)
35 , pathSearchMax_(config.pathSearchMax)
36 , standalone_(config.standalone)
37 , seq_(0)
38 , j_(registry.getJournal("OrderBookDB"))
39{
40}
41
44{
45 return std::make_unique<OrderBookDBImpl>(registry, config);
46}
47
48void
50{
51 if (!standalone_ && registry_.get().getOPs().isNeedNetworkLedger())
52 {
53 JLOG(j_.warn()) << "Eliding full order book update: no ledger";
54 return;
55 }
56
57 auto seq = seq_.load();
58
59 if (seq != 0)
60 {
61 if ((ledger->seq() > seq) && ((ledger->seq() - seq) < 25600))
62 return;
63
64 if ((ledger->seq() <= seq) && ((seq - ledger->seq()) < 16))
65 return;
66 }
67
68 if (seq_.exchange(ledger->seq()) != seq)
69 return;
70
71 JLOG(j_.debug()) << "Full order book update: " << seq << " to " << ledger->seq();
72
73 if (pathSearchMax_ != 0)
74 {
75 if (standalone_)
76 {
77 update(ledger);
78 }
79 else
80 {
81 // Shorten job name to fit Linux 15-char thread name limit with "j:" prefix
82 // "OB" + seq (max 9 digits) = 11 chars, + "j:" = 13 chars (fits in 15)
83 registry_.get().getJobQueue().addJob(
84 JtUpdatePf, "OB" + std::to_string(ledger->seq() % 1000000000), [this, ledger]() {
85 update(ledger);
86 });
87 }
88 }
89}
90
91void
93{
94 if (pathSearchMax_ == 0)
95 return; // pathfinding has been disabled
96
97 // A newer full update job is pending
98 if (auto const seq = seq_.load(); seq > ledger->seq())
99 {
100 JLOG(j_.debug()) << "Eliding update for " << ledger->seq()
101 << " because of pending update to later " << seq;
102 return;
103 }
104
105 decltype(allBooks_) allBooks;
106 decltype(xrpBooks_) xrpBooks;
107 decltype(domainBooks_) domainBooks;
108 decltype(xrpDomainBooks_) xrpDomainBooks;
109
110 allBooks.reserve(allBooks_.size());
111 xrpBooks.reserve(xrpBooks_.size());
112
113 JLOG(j_.debug()) << "Beginning update (" << ledger->seq() << ")";
114
115 // walk through the entire ledger looking for orderbook/AMM entries
116 int cnt = 0;
117
118 try
119 {
120 for (auto& sle : ledger->sles)
121 {
122 if (registry_.get().isStopping())
123 {
124 JLOG(j_.info()) << "Update halted because the process is stopping";
125 seq_.store(0);
126 return;
127 }
128
129 if (sle->getType() == ltDIR_NODE && sle->isFieldPresent(sfExchangeRate) &&
130 sle->getFieldH256(sfRootIndex) == sle->key())
131 {
132 Book book;
133
134 if (sle->isFieldPresent(sfTakerPaysCurrency))
135 {
136 Issue issue;
137 issue.currency = sle->getFieldH160(sfTakerPaysCurrency);
138 issue.account = sle->getFieldH160(sfTakerPaysIssuer);
139 book.in = issue;
140 }
141 else
142 {
143 XRPL_ASSERT(
144 sle->isFieldPresent(sfTakerPaysMPT),
145 "OrderBookDB::update, must be TakerPaysMPT");
146 book.in = sle->getFieldH192(sfTakerPaysMPT);
147 }
148 if (sle->isFieldPresent(sfTakerGetsCurrency))
149 {
150 Issue issue;
151 issue.currency = sle->getFieldH160(sfTakerGetsCurrency);
152 issue.account = sle->getFieldH160(sfTakerGetsIssuer);
153 book.out = issue;
154 }
155 else
156 {
157 XRPL_ASSERT(
158 sle->isFieldPresent(sfTakerGetsMPT),
159 "OrderBookDB::update, must be TakerGetsMPT");
160 book.out = sle->getFieldH192(sfTakerGetsMPT);
161 }
162 book.domain = (*sle)[~sfDomainID];
163
164 if (book.domain)
165 {
166 domainBooks[{book.in, *book.domain}].insert(book.out);
167 }
168 else
169 {
170 allBooks[book.in].insert(book.out);
171 }
172
173 if (book.domain && isXRP(book.out))
174 {
175 xrpDomainBooks.insert({book.in, *book.domain});
176 }
177 else if (isXRP(book.out))
178 {
179 xrpBooks.insert(book.in);
180 }
181
182 ++cnt;
183 }
184 else if (sle->getType() == ltAMM)
185 {
186 auto const asset1 = (*sle)[sfAsset];
187 auto const asset2 = (*sle)[sfAsset2];
188 auto addBook = [&](Asset const& in, Asset const& out) {
189 allBooks[in].insert(out);
190
191 if (isXRP(out))
192 xrpBooks.insert(in);
193
194 ++cnt;
195 };
196 addBook(asset1, asset2);
197 addBook(asset2, asset1);
198 }
199 }
200 }
201 catch (SHAMapMissingNode const& mn)
202 {
203 JLOG(j_.info()) << "Missing node in " << ledger->seq() << " during update: " << mn.what();
204 seq_.store(0);
205 return;
206 }
207
208 JLOG(j_.debug()) << "Update completed (" << ledger->seq() << "): " << cnt << " books found";
209
210 {
211 std::scoped_lock const sl(lock_);
212 allBooks_.swap(allBooks);
213 xrpBooks_.swap(xrpBooks);
214 domainBooks_.swap(domainBooks);
215 xrpDomainBooks_.swap(xrpDomainBooks);
216 }
217
218 registry_.get().getLedgerMaster().newOrderBookDB();
219}
220
221void
223{
224 bool const toXRP = isXRP(book.out);
225
226 std::scoped_lock const sl(lock_);
227
228 if (book.domain)
229 {
230 domainBooks_[{book.in, *book.domain}].insert(book.out);
231 }
232 else
233 {
234 allBooks_[book.in].insert(book.out);
235 }
236
237 if (book.domain && toXRP)
238 {
239 xrpDomainBooks_.insert({book.in, *book.domain});
240 }
241 else if (toXRP)
242 {
243 xrpBooks_.insert(book.in);
244 }
245}
246
247// return list of all orderbooks that want this issuerID and currencyID
250{
252
253 {
254 std::scoped_lock const sl(lock_);
255
256 auto getBooks = [&](auto const& container, auto const& key) {
257 if (auto it = container.find(key); it != container.end())
258 {
259 auto const& books = it->second;
260 ret.reserve(books.size());
261
262 for (auto const& gets : books)
263 ret.emplace_back(asset, gets, domain);
264 }
265 };
266
267 if (!domain)
268 {
269 getBooks(allBooks_, asset);
270 }
271 else
272 {
273 getBooks(domainBooks_, std::make_pair(asset, *domain));
274 }
275 }
276
277 return ret;
278}
279
280int
282{
283 std::scoped_lock const sl(lock_);
284
285 if (!domain)
286 {
287 if (auto it = allBooks_.find(asset); it != allBooks_.end())
288 return static_cast<int>(it->second.size());
289 }
290 else
291 {
292 if (auto it = domainBooks_.find({asset, *domain}); it != domainBooks_.end())
293 return static_cast<int>(it->second.size());
294 }
295
296 return 0;
297}
298
299bool
301{
302 std::scoped_lock const sl(lock_);
303 if (domain)
304 return xrpDomainBooks_.contains({asset, *domain});
305 return xrpBooks_.contains(asset);
306}
307
310{
311 hash_set<Book> result;
312
313 for (auto const& node : alTx.getMeta().getNodes())
314 {
315 try
316 {
317 if (node.getFieldU16(sfLedgerEntryType) == ltOFFER)
318 {
319 auto extract = [&](SField const& field) {
320 if (auto data = dynamic_cast<STObject const*>(node.peekAtPField(field)); data &&
321 data->isFieldPresent(sfTakerPays) && data->isFieldPresent(sfTakerGets))
322 {
323 result.emplace(
324 data->getFieldAmount(sfTakerGets).asset(),
325 data->getFieldAmount(sfTakerPays).asset(),
326 (*data)[~sfDomainID]);
327 }
328 };
329
330 if (node.getFName() == sfModifiedNode)
331 {
332 extract(sfPreviousFields);
333 }
334 else if (node.getFName() == sfCreatedNode)
335 {
336 extract(sfNewFields);
337 }
338 else if (node.getFName() == sfDeletedNode)
339 {
340 extract(sfFinalFields);
341 }
342 }
343 }
344 catch (std::exception const& ex)
345 {
346 // The bad node is skipped; other affected books in the same
347 // transaction are still returned. Logged at warn so a malformed
348 // offer node is visible to operators.
349 JLOG(j.warn()) << "affectedBooks: skipping malformed node (" << ex.what() << ")";
350 }
351 }
352
353 return result;
354}
355
356} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
Stream warn() const
Definition Journal.h:309
A transaction that is in a closed ledger.
TxMeta const & getMeta() const
Specifies an order book.
Definition Book.h:16
A currency issued by an account.
Definition Issue.h:13
Currency currency
Definition Issue.h:15
AccountID account
Definition Issue.h:16
hardened_hash_map< std::pair< Asset, Domain >, hardened_hash_set< Asset > > domainBooks_
void addOrderBook(Book const &book) override
Add an order book to track.
std::vector< Book > getBooksByTakerPays(Asset const &asset, std::optional< Domain > const &domain=std::nullopt) override
Get all order books that want a specific issue.
hash_set< Asset > xrpBooks_
void setup(std::shared_ptr< ReadView const > const &ledger) override
Initialize or update the order book database with a new ledger.
bool isBookToXRP(Asset const &asset, std::optional< Domain > const &domain=std::nullopt) override
Check if an order book to XRP exists for the given issue.
OrderBookDBImpl(ServiceRegistry &registry, OrderBookDBConfig const &config)
hardened_hash_map< Asset, hardened_hash_set< Asset > > allBooks_
std::atomic< std::uint32_t > seq_
int getBookSize(Asset const &asset, std::optional< Domain > const &domain=std::nullopt) override
Get the count of order books that want a specific issue.
hash_set< std::pair< Asset, Domain > > xrpDomainBooks_
void update(std::shared_ptr< ReadView const > const &ledger)
std::recursive_mutex lock_
std::reference_wrapper< ServiceRegistry > registry_
beast::Journal const j_
Identifies fields.
Definition SField.h:130
Service registry for dependency injection.
STArray & getNodes()
Definition TxMeta.h:69
T emplace_back(T... args)
T emplace(T... args)
T make_pair(T... args)
T make_unique(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::size_t extract(uint256 const &key)
Definition base_uint.h:655
bool isXRP(AccountID const &c)
Definition AccountID.h:70
std::unordered_set< Value, Hash, Pred, Allocator > hash_set
@ JtUpdatePf
Definition Job.h:37
hash_set< Book > affectedBooks(AcceptedLedgerTx const &alTx, beast::Journal const &j)
Extract the set of books affected by a transaction.
std::unique_ptr< OrderBookDB > makeOrderBookDb(ServiceRegistry &registry, OrderBookDBConfig const &config)
Create an OrderBookDB instance.
T reserve(T... args)
Configuration for OrderBookDB.
T to_string(T... args)
T what(T... args)