Clio  develop
The XRP Ledger API server.
Loading...
Searching...
No Matches
BookChangesHelper.hpp
Go to the documentation of this file.
1
2#pragma once
3
4#include "data/Types.hpp"
5#include "rpc/JS.hpp"
6#include "rpc/RPCHelpers.hpp"
7
8#include <boost/json/conversion.hpp>
9#include <boost/json/object.hpp>
10#include <boost/json/value.hpp>
11#include <xrpl/beast/utility/Zero.h>
12#include <xrpl/protocol/IOUAmount.h>
13#include <xrpl/protocol/Issue.h>
14#include <xrpl/protocol/LedgerFormats.h>
15#include <xrpl/protocol/LedgerHeader.h>
16#include <xrpl/protocol/SField.h>
17#include <xrpl/protocol/STAmount.h>
18#include <xrpl/protocol/STObject.h>
19#include <xrpl/protocol/STTx.h>
20#include <xrpl/protocol/TxFormats.h>
21#include <xrpl/protocol/XRPAmount.h>
22#include <xrpl/protocol/jss.h>
23
24#include <algorithm>
25#include <cstdint>
26#include <iterator>
27#include <map>
28#include <memory>
29#include <optional>
30#include <set>
31#include <string>
32#include <vector>
33
34namespace rpc {
35
39struct BookChange {
40 ripple::STAmount sideAVolume;
41 ripple::STAmount sideBVolume;
42 ripple::STAmount highRate;
43 ripple::STAmount lowRate;
44 ripple::STAmount openRate;
45 ripple::STAmount closeRate;
46 std::optional<ripple::uint256> domain;
47};
48
52class BookChanges final {
53public:
54 BookChanges() = delete; // only accessed via static handle function
55
62 [[nodiscard]] static std::vector<BookChange>
63 compute(std::vector<data::TransactionAndMetadata> const& transactions)
64 {
65 return HandlerImpl{}(transactions);
66 }
67
68private:
69 class HandlerImpl final {
70 std::map<std::string, BookChange> tally_;
71 std::optional<uint32_t> offerCancel_;
72
73 public:
74 [[nodiscard]] std::vector<BookChange>
75 operator()(std::vector<data::TransactionAndMetadata> const& transactions)
76 {
77 for (auto const& tx : transactions)
78 handleBookChange(tx);
79
80 // TODO: rewrite this with std::ranges when compilers catch up
81 std::vector<BookChange> changes;
82 std::transform(
83 std::make_move_iterator(std::begin(tally_)),
84 std::make_move_iterator(std::end(tally_)),
85 std::back_inserter(changes),
86 [](auto obj) { return obj.second; }
87 );
88 return changes;
89 }
90
91 private:
92 void
93 handleAffectedNode(ripple::STObject const& node)
94 {
95 auto const& metaType = node.getFName();
96 auto const nodeType = node.getFieldU16(ripple::sfLedgerEntryType);
97
98 // we only care about ripple::ltOFFER objects being modified or
99 // deleted
100 if (nodeType != ripple::ltOFFER || metaType == ripple::sfCreatedNode)
101 return;
102
103 // if either FF or PF are missing we can't compute
104 // but generally these are cancelled rather than crossed
105 // so skipping them is consistent
106 if (!node.isFieldPresent(ripple::sfFinalFields) ||
107 !node.isFieldPresent(ripple::sfPreviousFields))
108 return;
109
110 auto const& finalFields =
111 node.peekAtField(ripple::sfFinalFields).downcast<ripple::STObject>();
112 auto const& previousFields =
113 node.peekAtField(ripple::sfPreviousFields).downcast<ripple::STObject>();
114
115 // defensive case that should never be hit
116 if (!finalFields.isFieldPresent(ripple::sfTakerGets) ||
117 !finalFields.isFieldPresent(ripple::sfTakerPays) ||
118 !previousFields.isFieldPresent(ripple::sfTakerGets) ||
119 !previousFields.isFieldPresent(ripple::sfTakerPays))
120 return;
121
122 // filter out any offers deleted by explicit offer cancels
123 if (metaType == ripple::sfDeletedNode && offerCancel_ &&
124 finalFields.getFieldU32(ripple::sfSequence) == *offerCancel_)
125 return;
126
127 // compute the difference in gets and pays actually
128 // affected onto the offer
129 auto const deltaGets = finalFields.getFieldAmount(ripple::sfTakerGets) -
130 previousFields.getFieldAmount(ripple::sfTakerGets);
131 auto const deltaPays = finalFields.getFieldAmount(ripple::sfTakerPays) -
132 previousFields.getFieldAmount(ripple::sfTakerPays);
133
134 transformAndStore(deltaGets, deltaPays, finalFields[~ripple::sfDomainID]);
135 }
136
137 void
138 transformAndStore(
139 ripple::STAmount const& deltaGets,
140 ripple::STAmount const& deltaPays,
141 std::optional<ripple::uint256> const& domain
142 )
143 {
144 auto const g = to_string(deltaGets.issue());
145 auto const p = to_string(deltaPays.issue());
146
147 auto const noswap = [&]() {
148 if (isXRP(deltaGets))
149 return true;
150 return isXRP(deltaPays) ? false : (g < p);
151 }();
152
153 auto first = noswap ? deltaGets : deltaPays;
154 auto second = noswap ? deltaPays : deltaGets;
155
156 // defensively programmed, should (probably) never happen
157 if (second == beast::zero)
158 return;
159
160 auto const rate = divide(first, second, ripple::noIssue());
161
162 if (first < beast::zero)
163 first = -first;
164
165 if (second < beast::zero)
166 second = -second;
167
168 auto const key = noswap ? (g + '|' + p) : (p + '|' + g);
169 if (tally_.contains(key)) {
170 auto& entry = tally_.at(key);
171
172 entry.sideAVolume += first;
173 entry.sideBVolume += second;
174
175 if (entry.highRate < rate)
176 entry.highRate = rate;
177
178 if (entry.lowRate > rate)
179 entry.lowRate = rate;
180
181 entry.closeRate = rate;
182 entry.domain = domain;
183 } else {
184 tally_[key] = {
185 .sideAVolume = first,
186 .sideBVolume = second,
187 .highRate = rate,
188 .lowRate = rate,
189 .openRate = rate,
190 .closeRate = rate,
191 .domain = domain,
192 };
193 }
194 }
195
196 void
197 handleBookChange(data::TransactionAndMetadata const& blob)
198 {
199 auto const [tx, meta] = rpc::deserializeTxPlusMeta(blob);
200 if (!tx || !meta || !tx->isFieldPresent(ripple::sfTransactionType))
201 return;
202
203 offerCancel_ = shouldCancelOffer(tx);
204 for (auto const& node : meta->getFieldArray(ripple::sfAffectedNodes))
205 handleAffectedNode(node);
206 }
207
208 static std::optional<uint32_t>
209 shouldCancelOffer(std::shared_ptr<ripple::STTx const> const& tx)
210 {
211 switch (tx->getFieldU16(ripple::sfTransactionType)) {
212 // in future if any other ways emerge to cancel an offer
213 // this switch makes them easy to add
214 case ripple::ttOFFER_CANCEL:
215 case ripple::ttOFFER_CREATE:
216 if (tx->isFieldPresent(ripple::sfOfferSequence))
217 return tx->getFieldU32(ripple::sfOfferSequence);
218 [[fallthrough]];
219 default:
220 return std::nullopt;
221 }
222 }
223 };
224};
225
232inline void
233tag_invoke(boost::json::value_from_tag, boost::json::value& jv, BookChange const& change)
234{
235 auto amountStr = [](ripple::STAmount const& amount) -> std::string {
236 return isXRP(amount) ? to_string(amount.xrp()) : to_string(amount.iou());
237 };
238
239 auto currencyStr = [](ripple::STAmount const& amount) -> std::string {
240 return isXRP(amount) ? "XRP_drops" : to_string(amount.issue());
241 };
242
243 jv = {
244 {JS(currency_a), currencyStr(change.sideAVolume)},
245 {JS(currency_b), currencyStr(change.sideBVolume)},
246 {JS(volume_a), amountStr(change.sideAVolume)},
247 {JS(volume_b), amountStr(change.sideBVolume)},
248 {JS(high), to_string(change.highRate.iou())},
249 {JS(low), to_string(change.lowRate.iou())},
250 {JS(open), to_string(change.openRate.iou())},
251 {JS(close), to_string(change.closeRate.iou())},
252 };
253
254 if (change.domain.has_value())
255 jv.as_object()[JS(domain)] = ripple::to_string(*change.domain);
256}
257
265[[nodiscard]] boost::json::object
266computeBookChanges(
267 ripple::LedgerHeader const& lgrInfo,
268 std::vector<data::TransactionAndMetadata> const& transactions
269);
270
271} // namespace rpc
static std::vector< BookChange > compute(std::vector< data::TransactionAndMetadata > const &transactions)
Computes all book_changes for the given transactions.
Definition BookChangesHelper.hpp:63
This namespace contains all the RPC logic and handlers.
Definition AMMHelpers.cpp:18
std::pair< std::shared_ptr< ripple::STTx const >, std::shared_ptr< ripple::STObject const > > deserializeTxPlusMeta(data::TransactionAndMetadata const &blobs)
Deserialize a TransactionAndMetadata into a pair of STTx and STObject.
Definition RPCHelpers.cpp:185
void tag_invoke(boost::json::value_from_tag, boost::json::value &jv, BookChange const &change)
Implementation of value_from for BookChange type.
Definition BookChangesHelper.hpp:233
Represents an entry in the book_changes' changes array.
Definition BookChangesHelper.hpp:39