xrpld
Loading...
Searching...
No Matches
LedgerToJson.cpp
1#include <xrpld/app/ledger/LedgerToJson.h>
2
3#include <xrpld/app/ledger/LedgerMaster.h>
4#include <xrpld/app/misc/DeliverMax.h>
5#include <xrpld/app/misc/TxQ.h>
6#include <xrpld/rpc/CTID.h>
7#include <xrpld/rpc/Context.h>
8#include <xrpld/rpc/detail/SyntheticFields.h>
9
10#include <xrpl/basics/Log.h>
11#include <xrpl/basics/base_uint.h>
12#include <xrpl/basics/chrono.h>
13#include <xrpl/basics/strHex.h>
14#include <xrpl/beast/utility/instrumentation.h>
15#include <xrpl/core/NetworkIDService.h>
16#include <xrpl/json/json_value.h>
17#include <xrpl/ledger/helpers/TokenHelpers.h>
18#include <xrpl/protocol/AccountID.h>
19#include <xrpl/protocol/ApiVersion.h>
20#include <xrpl/protocol/LedgerHeader.h>
21#include <xrpl/protocol/SField.h>
22#include <xrpl/protocol/STObject.h>
23#include <xrpl/protocol/STTx.h>
24#include <xrpl/protocol/Serializer.h>
25#include <xrpl/protocol/TER.h>
26#include <xrpl/protocol/TxFormats.h>
27#include <xrpl/protocol/Units.h>
28#include <xrpl/protocol/XRPAmount.h>
29#include <xrpl/protocol/jss.h>
30#include <xrpl/protocol/serialize.h>
31
32#include <cstdint>
33#include <exception>
34#include <memory>
35#include <string>
36
37namespace xrpl {
38
39namespace {
40
41bool
42isFull(LedgerFill const& fill)
43{
44 return (fill.options & static_cast<int>(LedgerFill::Options::Full)) != 0;
45}
46
47bool
48isExpanded(LedgerFill const& fill)
49{
50 return isFull(fill) || ((fill.options & static_cast<int>(LedgerFill::Options::Expand)) != 0);
51}
52
53bool
54isBinary(LedgerFill const& fill)
55{
56 return (fill.options & static_cast<int>(LedgerFill::Options::Binary)) != 0;
57}
58
59void
60fillJson(json::Value& json, bool closed, LedgerHeader const& info, bool bFull, unsigned apiVersion)
61{
62 json[jss::parent_hash] = to_string(info.parentHash);
63 json[jss::ledger_index] =
64 (apiVersion > 1) ? json::Value(info.seq) : json::Value(std::to_string(info.seq));
65
66 if (closed)
67 {
68 json[jss::closed] = true;
69 }
70 else if (!bFull)
71 {
72 json[jss::closed] = false;
73 return;
74 }
75
76 json[jss::ledger_hash] = to_string(info.hash);
77 json[jss::transaction_hash] = to_string(info.txHash);
78 json[jss::account_hash] = to_string(info.accountHash);
79 json[jss::total_coins] = to_string(info.drops);
80
81 json[jss::close_flags] = info.closeFlags;
82
83 // Always show fields that contribute to the ledger hash
84 json[jss::parent_close_time] = info.parentCloseTime.time_since_epoch().count();
85 json[jss::close_time] = info.closeTime.time_since_epoch().count();
86 json[jss::close_time_resolution] = info.closeTimeResolution.count();
87
88 if (info.closeTime != NetClock::time_point{})
89 {
90 json[jss::close_time_human] = to_string(info.closeTime);
91 if (!getCloseAgree(info))
92 json[jss::close_time_estimated] = true;
93 json[jss::close_time_iso] = toStringIso(info.closeTime);
94 }
95}
96
97void
98fillJsonBinary(json::Value& json, bool closed, LedgerHeader const& info)
99{
100 if (!closed)
101 {
102 json[jss::closed] = false;
103 }
104 else
105 {
106 json[jss::closed] = true;
107
108 Serializer s;
109 addRaw(info, s);
110 json[jss::ledger_data] = strHex(s.peekData());
111 }
112}
113
114json::Value
115fillJsonTx(
116 LedgerFill const& fill,
117 bool bBinary,
118 bool bExpanded,
119 std::shared_ptr<STTx const> const& txn,
120 std::shared_ptr<STObject const> const& stMeta)
121{
122 if (!bExpanded)
123 return to_string(txn->getTransactionID());
124
125 json::Value txJson{json::ValueType::Object};
126 auto const txnType = txn->getTxnType();
127 if (bBinary)
128 {
129 txJson[jss::tx_blob] = serializeHex(*txn);
130 if (fill.context->apiVersion > 1)
131 txJson[jss::hash] = to_string(txn->getTransactionID());
132
133 auto const jsonMeta = (fill.context->apiVersion > 1 ? jss::meta_blob : jss::meta);
134 if (stMeta)
135 txJson[jsonMeta] = serializeHex(*stMeta);
136 }
137 else if (fill.context->apiVersion > 1)
138 {
139 copyFrom(txJson[jss::tx_json], txn->getJson(JsonOptions::Values::DisableApiPriorV2, false));
140 txJson[jss::hash] = to_string(txn->getTransactionID());
141 rpc::insertDeliverMax(txJson[jss::tx_json], txnType, fill.context->apiVersion);
142
143 if (stMeta)
144 {
145 txJson[jss::meta] = stMeta->getJson(JsonOptions::Values::None);
146
147 // Insert all synthetic fields
149 txJson[jss::meta],
150 fill.ledger,
151 txn,
152 {txn->getTransactionID(), fill.ledger.seq(), *stMeta});
153 }
154
155 if (!fill.ledger.open())
156 txJson[jss::ledger_hash] = to_string(fill.ledger.header().hash);
157
158 bool const validated = fill.context->ledgerMaster.isValidated(fill.ledger);
159 txJson[jss::validated] = validated;
160 if (validated)
161 {
162 auto const seq = fill.ledger.seq();
163 txJson[jss::ledger_index] = seq;
164 if (fill.closeTime)
165 txJson[jss::close_time_iso] = toStringIso(*fill.closeTime);
166 }
167 }
168 else
169 {
170 copyFrom(txJson, txn->getJson(JsonOptions::Values::None));
171 rpc::insertDeliverMax(txJson, txnType, fill.context->apiVersion);
172 if (stMeta)
173 {
174 txJson[jss::metaData] = stMeta->getJson(JsonOptions::Values::None);
175
176 // Insert all synthetic fields
178 txJson[jss::metaData],
179 fill.ledger,
180 txn,
181 {txn->getTransactionID(), fill.ledger.seq(), *stMeta});
182 }
183 }
184
185 // compute outgoing CTID
186 if (stMeta && stMeta->isFieldPresent(sfTransactionIndex))
187 {
188 uint32_t const lgrSeq = fill.ledger.seq();
189 uint32_t const txnIdx = stMeta->getFieldU32(sfTransactionIndex);
190 uint32_t netID = fill.context->app.getNetworkIDService().getNetworkID();
191 if (txn->isFieldPresent(sfNetworkID))
192 netID = txn->getFieldU32(sfNetworkID);
193
194 if (auto ctid = rpc::encodeCTID(lgrSeq, txnIdx, netID))
195 txJson[jss::ctid] = *ctid;
196 }
197
198 if (((fill.options & static_cast<int>(LedgerFill::Options::OwnerFunds)) != 0) &&
199 txn->getTxnType() == ttOFFER_CREATE)
200 {
201 auto const account = txn->getAccountID(sfAccount);
202 auto const amount = txn->getFieldAmount(sfTakerGets);
203
204 // If the offer create is not self funded then add the
205 // owner balance
206 if (account != amount.getIssuer())
207 {
208 auto const ownerFunds = accountFunds(
209 fill.ledger,
210 account,
211 amount,
214 beast::Journal{beast::Journal::getNullSink()});
215 txJson[jss::owner_funds] = ownerFunds.getText();
216 }
217 }
218
219 return txJson;
220}
221
222void
223fillJsonTx(json::Value& json, LedgerFill const& fill)
224{
225 auto& txns = json[jss::transactions] = json::ValueType::Array;
226 auto bBinary = isBinary(fill);
227 auto bExpanded = isExpanded(fill);
228
229 try
230 {
231 auto appendAll = [&](auto const& txs) {
232 for (auto& i : txs)
233 {
234 txns.append(fillJsonTx(fill, bBinary, bExpanded, i.first, i.second));
235 }
236 };
237
238 appendAll(fill.ledger.txs);
239 }
240 catch (std::exception const& ex)
241 {
242 // Nothing the user can do about this.
243 if (fill.context != nullptr)
244 {
245 JLOG(fill.context->j.error()) << "Exception in " << __func__ << ": " << ex.what();
246 }
247 }
248}
249
250void
251fillJsonState(json::Value& json, LedgerFill const& fill)
252{
253 auto& ledger = fill.ledger;
254 auto& array = json[jss::accountState] = json::ValueType::Array;
255 auto expanded = isExpanded(fill);
256 auto binary = isBinary(fill);
257
258 for (auto const& sle : ledger.sles)
259 {
260 if (binary)
261 {
262 auto& obj = array.append(json::ValueType::Object);
263 obj[jss::hash] = to_string(sle->key());
264 obj[jss::tx_blob] = serializeHex(*sle);
265 }
266 else if (expanded)
267 {
268 array.append(sle->getJson(JsonOptions::Values::None));
269 }
270 else
271 {
272 array.append(to_string(sle->key()));
273 }
274 }
275}
276
277void
278fillJsonQueue(json::Value& json, LedgerFill const& fill)
279{
280 auto& queueData = json[jss::queue_data] = json::ValueType::Array;
281 auto bBinary = isBinary(fill);
282 auto bExpanded = isExpanded(fill);
283
284 for (auto const& tx : fill.txQueue)
285 {
286 auto& txJson = queueData.append(json::ValueType::Object);
287 txJson[jss::fee_level] = to_string(tx.feeLevel);
288 if (tx.lastValid)
289 txJson[jss::LastLedgerSequence] = *tx.lastValid;
290
291 txJson[jss::fee] = to_string(tx.consequences.fee());
292 auto const spend = tx.consequences.potentialSpend() + tx.consequences.fee();
293 txJson[jss::max_spend_drops] = to_string(spend);
294 txJson[jss::auth_change] = tx.consequences.isBlocker();
295
296 txJson[jss::account] = to_string(tx.account);
297 txJson["retries_remaining"] = tx.retriesRemaining;
298 txJson["preflight_result"] = transToken(tx.preflightResult);
299 if (tx.lastResult)
300 txJson["last_result"] = transToken(*tx.lastResult);
301
302 auto&& temp = fillJsonTx(fill, bBinary, bExpanded, tx.txn, nullptr);
303 if (temp.isObject())
304 {
305 if (fill.context->apiVersion > 1)
306 {
307 copyFrom(txJson, temp);
308 }
309 else
310 {
311 copyFrom(txJson[jss::tx], temp);
312 }
313 }
314 else if (fill.context->apiVersion > 1)
315 {
316 txJson[jss::hash] = temp;
317 }
318 else
319 {
320 txJson[jss::tx] = temp;
321 }
322 }
323}
324
325void
326fillJson(json::Value& json, LedgerFill const& fill)
327{
328 // TODO: what happens if bBinary and bExtracted are both set?
329 // Is there a way to report this back?
330 auto bFull = isFull(fill);
331 if (isBinary(fill))
332 {
333 fillJsonBinary(json, !fill.ledger.open(), fill.ledger.header());
334 }
335 else
336 {
337 fillJson(
338 json,
339 !fill.ledger.open(),
340 fill.ledger.header(),
341 bFull,
342 ((fill.context != nullptr) ? fill.context->apiVersion
344 }
345
346 if (bFull || ((fill.options & static_cast<int>(LedgerFill::Options::DumpTxrp)) != 0))
347 fillJsonTx(json, fill);
348
349 if (bFull || ((fill.options & static_cast<int>(LedgerFill::Options::DumpState)) != 0))
350 fillJsonState(json, fill);
351}
352
353} // namespace
354
355void
357{
358 auto& object = json[jss::ledger] = json::ValueType::Object;
359 fillJson(object, fill);
360
361 if (((fill.options & static_cast<int>(LedgerFill::Options::DumpQueue)) != 0) &&
362 !fill.txQueue.empty())
363 {
364 fillJsonQueue(json, fill);
365 }
366}
367
369getJson(LedgerFill const& fill)
370{
372 fillJson(json, fill);
373 return json;
374}
375
376void
378{
379 if (!to)
380 { // Short circuit this very common case.
381 to = from;
382 }
383 else
384 {
385 // TODO: figure out if there is a way to remove this clause
386 // or check that it does/needs to do a deep copy
387 XRPL_ASSERT(from.isObjectOrNull(), "copyFrom : invalid input type");
388 auto const members = from.getMemberNames();
389 for (auto const& m : members)
390 to[m] = from[m];
391 }
392}
393
394} // namespace xrpl
Represents a JSON value.
Definition json_value.h:117
Value & append(Value const &value)
Append value to array at the end.
Members getMemberNames() const
Return a list of the member names.
bool isObjectOrNull() const
std::chrono::time_point< NetClock > time_point
Definition chrono.h:48
T fill(T... args)
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
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:220
static constexpr auto kApiMaximumSupportedVersion
Definition ApiVersion.h:43
std::optional< std::string > encodeCTID(uint32_t ledgerSeq, uint32_t txnIndex, uint32_t networkID) noexcept
Encodes ledger sequence, transaction index, and network ID into a CTID string.
Definition CTID.h:36
void insertDeliverMax(json::Value &txJson, TxType txnType, unsigned int apiVersion)
Copy Amount field to DeliverMax field in transaction output JSON.
Definition DeliverMax.cpp:9
void insertAllSyntheticInJson(json::Value &metadata, ReadView const &ledger, std::shared_ptr< STTx const > const &transaction, TxMeta const &transactionMeta)
Adds all synthetic fields to transaction metadata JSON.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:13
void addJson(json::Value &json, LedgerFill const &fill)
Given a Ledger and options, fill a json::Value with a description of the ledger.
std::string transToken(TER code)
Definition TER.cpp:257
bool getCloseAgree(LedgerHeader const &info)
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
std::string toStringIso(date::sys_time< Duration > tp)
Definition chrono.h:70
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
void addRaw(LedgerHeader const &, Serializer &, bool includeHash=false)
std::string serializeHex(STObject const &o)
Serialize an object to a hex string.
Definition serialize.h:28
json::Value getJson(LedgerFill const &fill)
Return a new json::Value representing the ledger with given options.
void copyFrom(json::Value &to, json::Value const &from)
Copy all the keys and values from one object into another.
Information about the notional ledger backing the view.
T to_string(T... args)
T what(T... args)