xrpld
Loading...
Searching...
No Matches
BookChanges.h
1#pragma once
2
3#include <xrpl/basics/base_uint.h>
4#include <xrpl/beast/utility/Zero.h>
5#include <xrpl/json/json_value.h>
6#include <xrpl/protocol/Asset.h>
7#include <xrpl/protocol/Issue.h>
8#include <xrpl/protocol/LedgerFormats.h>
9#include <xrpl/protocol/MPTIssue.h>
10#include <xrpl/protocol/SField.h>
11#include <xrpl/protocol/STAmount.h>
12#include <xrpl/protocol/STObject.h>
13#include <xrpl/protocol/TxFormats.h>
14#include <xrpl/protocol/jss.h>
15
16#include <cstdint>
17#include <map>
18#include <memory>
19#include <optional>
20#include <sstream>
21#include <string>
22#include <tuple>
23
24namespace json {
25class Value;
26} // namespace json
27
28namespace xrpl {
29
30class ReadView;
31class Transaction;
32class TxMeta;
33class STTx;
34
35namespace rpc {
36
37template <class L>
38json::Value
40{
44 STAmount, // side A volume
45 STAmount, // side B volume
46 STAmount, // high rate
47 STAmount, // low rate
48 STAmount, // open rate
49 STAmount, // close rate
50 std::optional<uint256>>> // optional: domain id
51 tally;
52
53 for (auto& tx : lpAccepted->txs)
54 {
55 if (!tx.first || !tx.second || !tx.first->isFieldPresent(sfTransactionType))
56 continue;
57
58 std::optional<uint32_t> offerCancel;
59 uint16_t const tt = tx.first->getFieldU16(sfTransactionType);
60 switch (tt)
61 {
62 case ttOFFER_CANCEL:
63 case ttOFFER_CREATE: {
64 if (tx.first->isFieldPresent(sfOfferSequence))
65 offerCancel = tx.first->getFieldU32(sfOfferSequence);
66 break;
67 }
68 // in future if any other ways emerge to cancel an offer
69 // this switch makes them easy to add
70 default:
71 break;
72 }
73
74 for (auto const& node : tx.second->getFieldArray(sfAffectedNodes))
75 {
76 SField const& metaType = node.getFName();
77 uint16_t const nodeType = node.getFieldU16(sfLedgerEntryType);
78
79 // we only care about ltOFFER objects being modified or
80 // deleted
81 if (nodeType != ltOFFER || metaType == sfCreatedNode)
82 continue;
83
84 // if either FF or PF are missing we can't compute
85 // but generally these are cancelled rather than crossed
86 // so skipping them is consistent
87 if (!node.isFieldPresent(sfFinalFields) || !node.isFieldPresent(sfPreviousFields))
88 continue;
89
90 auto const& ffBase = node.peekAtField(sfFinalFields);
91 auto const& finalFields = ffBase.template downcast<STObject>();
92 auto const& pfBase = node.peekAtField(sfPreviousFields);
93 auto const& previousFields = pfBase.template downcast<STObject>();
94
95 // defensive case that should never be hit
96 if (!finalFields.isFieldPresent(sfTakerGets) ||
97 !finalFields.isFieldPresent(sfTakerPays) ||
98 !previousFields.isFieldPresent(sfTakerGets) ||
99 !previousFields.isFieldPresent(sfTakerPays))
100 continue;
101
102 // filter out any offers deleted by explicit offer cancels
103 if (metaType == sfDeletedNode && offerCancel &&
104 finalFields.getFieldU32(sfSequence) == *offerCancel)
105 continue;
106
107 // compute the difference in gets and pays actually
108 // affected onto the offer
109 STAmount const deltaGets = finalFields.getFieldAmount(sfTakerGets) -
110 previousFields.getFieldAmount(sfTakerGets);
111 STAmount const deltaPays = finalFields.getFieldAmount(sfTakerPays) -
112 previousFields.getFieldAmount(sfTakerPays);
113
114 std::string const g{to_string(deltaGets.asset())};
115 std::string const p{to_string(deltaPays.asset())};
116
117 bool const noswap = isXRP(deltaGets) || (!isXRP(deltaPays) && (g < p));
118
119 STAmount first = noswap ? deltaGets : deltaPays;
120 STAmount second = noswap ? deltaPays : deltaGets;
121
122 // defensively programmed, should (probably) never happen
123 if (second == beast::kZero)
124 continue;
125
126 STAmount const rate = divide(first, second, noIssue());
127
128 if (first < beast::kZero)
129 first = -first;
130
131 if (second < beast::kZero)
132 second = -second;
133
135 if (noswap)
136 {
137 ss << g << "|" << p;
138 }
139 else
140 {
141 ss << p << "|" << g;
142 }
143
144 std::optional<uint256> const domain = finalFields[~sfDomainID];
145
146 std::string const key{ss.str()};
147
148 if (!tally.contains(key))
149 {
150 tally[key] = {
151 first, // side A vol
152 second, // side B vol
153 rate, // high
154 rate, // low
155 rate, // open
156 rate, // close
157 domain};
158 }
159 else
160 {
161 // increment volume
162 auto& entry = tally[key];
163
164 std::get<0>(entry) += first; // side A vol
165 std::get<1>(entry) += second; // side B vol
166
167 if (std::get<2>(entry) < rate) // high
168 std::get<2>(entry) = rate;
169
170 if (std::get<3>(entry) > rate) // low
171 std::get<3>(entry) = rate;
172
173 std::get<5>(entry) = rate; // close
174 std::get<6>(entry) = domain; // domain
175 }
176 }
177 }
178
180 jvObj[jss::type] = "bookChanges";
181
182 // retrieve validated information from LedgerHeader class
183 jvObj[jss::validated] = lpAccepted->header().validated;
184 jvObj[jss::ledger_index] = lpAccepted->header().seq;
185 jvObj[jss::ledger_hash] = to_string(lpAccepted->header().hash);
186 jvObj[jss::ledger_time] =
187 json::Value::UInt(lpAccepted->header().closeTime.time_since_epoch().count());
188
189 jvObj[jss::changes] = json::ValueType::Array;
190
191 auto volToStr = [](STAmount const& vol) {
192 return vol.asset().visit(
193 [&](Issue const& issue) {
194 if (isXRP(issue))
195 return to_string(vol.xrp());
196 return to_string(vol.iou());
197 },
198 [&](MPTIssue const&) { return to_string(vol.mpt()); });
199 };
200
201 for (auto const& entry : tally)
202 {
203 json::Value& inner = jvObj[jss::changes].append(json::ValueType::Object);
204
205 STAmount const volA = std::get<0>(entry.second);
206 STAmount const volB = std::get<1>(entry.second);
207
208 volA.asset().visit(
209 [&](Issue const&) {
210 inner[jss::currency_a] = (isXRP(volA) ? "XRP_drops" : to_string(volA.asset()));
211 },
212 [&](MPTIssue const&) { inner[jss::mpt_issuance_id_a] = to_string(volA.asset()); });
213
214 volB.asset().visit(
215 [&](Issue const&) {
216 inner[jss::currency_b] = (isXRP(volB) ? "XRP_drops" : to_string(volB.asset()));
217 },
218 [&](MPTIssue const&) { inner[jss::mpt_issuance_id_b] = to_string(volB.asset()); });
219
220 inner[jss::volume_a] = volToStr(volA);
221 inner[jss::volume_b] = volToStr(volB);
222
223 inner[jss::high] = to_string(std::get<2>(entry.second).iou());
224 inner[jss::low] = to_string(std::get<3>(entry.second).iou());
225 inner[jss::open] = to_string(std::get<4>(entry.second).iou());
226 inner[jss::close] = to_string(std::get<5>(entry.second).iou());
227
228 std::optional<uint256> const domain = std::get<6>(entry.second);
229 if (domain)
230 inner[jss::domain] = to_string(*domain);
231 }
232
233 return jvObj;
234}
235
236} // namespace rpc
237} // namespace xrpl
Represents a JSON value.
Definition json_value.h:117
json::UInt UInt
Definition json_value.h:124
Value & append(Value const &value)
Append value to array at the end.
constexpr auto visit(Visitors &&... visitors) const -> decltype(auto)
Definition Asset.h:117
A currency issued by an account.
Definition Issue.h:18
A view into a ledger.
Definition ReadView.h:41
Identifies fields.
Definition SField.h:132
Asset const & asset() const
Definition STAmount.h:496
constexpr Zero kZero
Definition Zero.h:30
JSON (JavaScript Object Notation).
Definition json_errors.h:5
@ Array
array value (ordered list)
Definition json_value.h:28
@ Object
object value (collection of name/value pairs).
Definition json_value.h:29
API version numbers used in later API versions.
Definition ApiVersion.h:36
json::Value computeBookChanges(std::shared_ptr< L const > const &lpAccepted)
Definition BookChanges.h:39
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
STAmount divide(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:69
bool isXRP(AccountID const &c)
Definition AccountID.h:84
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
Issue const & noIssue()
Returns an asset specifier that represents no account and currency.
Definition Issue.h:118
T str(T... args)