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